F# 元组类型的模式匹配

发布于 2024-08-20 11:43:57 字数 987 浏览 10 评论 0原文

我有一个柯里化函数,我希望它支持不同类型的参数,这些参数不属于继承关系:

type MyType1 = A | B of float
type MyType2 = C | D of int

我试图做的是:

let func x y =
    match (x, y) with
    | :? Tuple<MyType1, MyType1> -> "1, 1"
    | _ -> "..."

但是这是不可能的。 F# 抱怨:

类型 ''a * 'b' 没有任何适当的子类型,不能用作类型测试或运行时强制的源。

执行此操作的优雅方法是什么?

编辑:让我尝试澄清这一点。

我有两种相似但不同的类型。我可以非常轻松地将一种类型转换为另一种类型。我想定义一个将作用于这些类型的实体的二元操作,但我想向客户端公开单个操作。

也就是说,不是提供:

let op11 (x : MyType1) (y : MyType1) = // do something useful
let op12 (x : MyType1) (y : MyType2) =
    // convert y to MyType1
    let y' = // ...
    // dispatch to op11
    op11 x y'
let op21 (x : MyType2) (y : MyType1) = // similar
let op22 (x : MyType2) (y : MyType2) = // similar

我想要的是向客户端代码公开单个函数:

let op (x : obj) (y : obj) = // ...

这就像模拟方法重载的行为,但使用柯里化函数。

I have a curried function that I'd like it to support different types of parameters, that are not on a inheritance relationship:

type MyType1 = A | B of float
type MyType2 = C | D of int

What I tried to do is:

let func x y =
    match (x, y) with
    | :? Tuple<MyType1, MyType1> -> "1, 1"
    | _ -> "..."

However this is not possible. F# complains:

The type ''a * 'b' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion.

What is an elegant way to do this?

EDIT: Let me try to clarify this.

I have two similar, but distinct, types. I can very easily convert one type to another. I want to define a binary operation that will act on entities of those types, but I'd like to expose a single operation to the client.

That is, instead of providing:

let op11 (x : MyType1) (y : MyType1) = // do something useful
let op12 (x : MyType1) (y : MyType2) =
    // convert y to MyType1
    let y' = // ...
    // dispatch to op11
    op11 x y'
let op21 (x : MyType2) (y : MyType1) = // similar
let op22 (x : MyType2) (y : MyType2) = // similar

what I would like is to expose a single function to client code:

let op (x : obj) (y : obj) = // ...

This is like simulating the behavior of method overloading, but with curried functions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

看轻我的陪伴 2024-08-27 11:43:57

您的代码不起作用,因为 F# 将参数类型概括为类型参数。我认为您无法动态测试类型 'a * 'b 是否可以转换为类型 MyType1 * MyType2 (尽管这对我来说有点令人困惑)。在任何情况下,您都可以编写一个函数,该函数接受两个 obj 类型的参数,并使用两个 :? 模式分别测试它们:

type MyType1 = A | B of float 
type MyType2 = C | D of int

let func (x:obj) (y:obj) = 
    match (x, y) with 
    | (:? MyType1 as x1), (:? MyType1 as x2) -> 
        printfn "%A %A" x1 x2
    | _ -> 
        printfn "something else" 

func A (B 3.0) // A B 3.0
func A (D 42)  // something else

无论如何,知道为什么这样做会很有趣你想这样做吗?可能有更好的解决方案...

编辑 (2) 因此,从 T1T2 的所有 4 个二元素组合中,您想要可以采用 3 的函数。正确吗(T1 * T1T1 * T2T2 * T2)?在这种情况下,您无法编写完全安全的柯里化函数,因为第二个参数的类型将“依赖于”第一个参数的类型(如果第一个参数的类型为 T2,则第二个参数也必须是 T2 (否则也可以是 T1))。

您可以编写一个安全的非柯里化函数,该函数采用以下类型的参数:

type MyArg = Comb1 of T1 * T1 | Comb2 of T1 * T2 | Comb3 of T2 * T2

该函数的类型为 MyArg ->字符串
如果您想要一个柯里化函数,您可以定义一个类型,允许您使用 T1T2 作为第一个和第二个参数。

type MyArg = First of T1 | Second of T2

然后,你的柯里化函数将是 MyArg ->我的Arg ->字符串。但请注意,如果不允许使用一种参数类型组合(如果我理解正确,则不应允许使用 T2 * T1)。在这种情况下,您的函数只需抛出异常或类似的东西。

Your code doesn't work, because F# generalizes the type of arguments to a type parameter. I think you can't dynamically test whether a type 'a * 'b can be converted to type MyType1 * MyType2 (though this is a bit confusing to me). In any case, you can write a function that takes two arguments of type obj and tests them separately using two :? patterns:

type MyType1 = A | B of float 
type MyType2 = C | D of int

let func (x:obj) (y:obj) = 
    match (x, y) with 
    | (:? MyType1 as x1), (:? MyType1 as x2) -> 
        printfn "%A %A" x1 x2
    | _ -> 
        printfn "something else" 

func A (B 3.0) // A B 3.0
func A (D 42)  // something else

Anyway, it would be interesting to know why do you want to do this? There may be a better solution...

EDIT (2) So, from all 4 two-element combinations of T1 and T2, you want the function that can take 3. Is that correct (T1 * T1, T1 * T2 and T2 * T2)? In that case, you can't write a fully safe curried function, because the type of second argument would "depend" on the type of first argument (if the first argument has a type T2, then the second argument also has to be T2 (otherwise it can be T1 too)).

You can write a safe non-curried function that takes an argument of the following type:

type MyArg = Comb1 of T1 * T1 | Comb2 of T1 * T2 | Comb3 of T2 * T2

The type of the function would be MyArg -> string.
If you want a curried function, you can define a type which allows you to use either T1 or T2 as both first and second argument.

type MyArg = First of T1 | Second of T2

Then, your curried function will be MyArg -> MyArg -> string. But note that if one combination of argument types is not allowed (if I understand you correctly, T2 * T1 shouldn't be allowed). In this case, your function will simply have to throw an exception or something like that.

蓝天白云 2024-08-27 11:43:57

本质上有三种不同的方法可以实现这一点。

第一个是按照您的建议通过向上转换为 obj 来牺牲静态类型:

let func x y =
  match box x, box y with
  | (:? MyType1 as x), (:? MyType1 as y) ->
      ...

这实际上总是一个糟糕的解决方案,因为它会导致引入不必要的运行时类型检查:

  | _ -> invalidArg "x" "Run-time type error"

唯一 我看到这个工作很好的地方是专门为用户从 F# 交互式会话中调用而设计的库代码,其中编译时和运行时类型错误实际上同时发生,并且动态类型可以更简洁。例如,我们的 F# for Visualization 库允许用户尝试可视化任何值使用此技术为不同(已知)类型调用自定义可视化例程的任何类型(了解更多)。

第二种是将两种单独的类型替换为单一类型:

type MyType1 = A | B of float | C | D of int   

第三种解决方案是引入一种新类型来统一这两种类型:

type MyType = MyType1 of MyType1 | MyType2 of MyType2

There are essentially three different ways to accomplish this.

The first is to sacrifice static typing by upcasting to obj as you suggested:

let func x y =
  match box x, box y with
  | (:? MyType1 as x), (:? MyType1 as y) ->
      ...

This is virtually always an awful solution because it results in the introduction of unnecessary run-time type checks:

  | _ -> invalidArg "x" "Run-time type error"

The only place I have seen this work well is library code designed specifically for users to invoke from F# interactive sessions where compile-time and run-time type errors effectively occur at the same time and dynamic typing can be more concise. For example, our F# for Visualization library allows the user to try to visualize any value of any type using this technique to invoke custom visualization routines for different (known) types (read more).

The second is to replace your two separate types with a single type:

type MyType1 = A | B of float | C | D of int   

The third solution is to introduce a new type that unifies just those two types:

type MyType = MyType1 of MyType1 | MyType2 of MyType2
绿萝 2024-08-27 11:43:57

我有两个相似但不同的,
类型。我可以很容易地转换一个
键入另一个。我想定义一个
将作用于的二元运算
这些类型的实体,但我想
将单个操作暴露给
客户端。

这个函数应该决定哪一个
要调用的 opXY,正确向下转型
类型。

这是其中正确答案不是“这就是你如何做”,而是“不要那样做”的情况之一。如果您不打算坚持使用 F# 的习惯用法和类型检查器,那么使用 F# 就没有真正的优势。

从我的角度来看,如果您的类型非常相似,那么它们应该合并为同一类型:

type MyType = A | B of float | C | D of int

除此之外,您可以将两种类型包装在另一种类型中:

type composite =
    | MyType1Tuple of MyType1 * MyType1
    | MyType2Tuple of MyType2 * MyType2

另一层间接永远不会伤害任何人。但至少现在您的客户可以用另一个对象包装对象而不会失去类型安全性。

除此之外,请为不同类型公开两个单独的方法。

I have two similar, but distinct,
types. I can very easily convert one
type to another. I want to define a
binary operation that will act on
entities of those types, but I'd like
to expose a single operation to the
client.

This function should decide which of
the opXY to call, properly downcasting
types.

This is one of those cases where the correct answer isn't "here's how you do it", but instead "don't do it like that". There's no real advantage using F# if you aren't going to stick with its idioms and type-checker.

From my point of view, if your types are so similar, they should be merged into the same type:

type MyType = A | B of float | C | D of int

Barring that, you can wrap your two types in another type:

type composite =
    | MyType1Tuple of MyType1 * MyType1
    | MyType2Tuple of MyType2 * MyType2

Another layer of indirection never hurt anyone. But at least now your clients can wrap the objects with another without losing type safety.

And barring all of that, expose two separate methods for your different types.

巴黎盛开的樱花 2024-08-27 11:43:57

这闻起来很可疑,你应该描述更大的问题背景,因为看起来你不应该处于这种情况。那是说:

type MyType1 = A | B of float 
type MyType2 = C | D of int 

// imagine this adds floats, and C -> A (=0.0) and D -> B
let DoIt x y =
    match x, y with
    | A, A -> 0.0
    | A, B z -> z
    | B z, A -> z
    | B z1, B z2 -> z1 + z2

let Convert x =
    match x with
    | C -> A
    | D i -> B (float i)

let Func (x:obj) (y:obj) =
    match x, y with
    | (:? MyType2 as xx), (:? MyType2 as yy) -> DoIt (Convert xx) (Convert yy)
    | (:? MyType1 as xx), (:? MyType2 as yy) -> DoIt xx (Convert yy)    
    | (:? MyType2 as xx), (:? MyType1 as yy) -> DoIt (Convert xx) yy
    | (:? MyType1 as xx), (:? MyType1 as yy) -> DoIt xx yy    
    | _ -> failwith "bad args"

This smells very fishy, you should describe the bigger problem context as it seems like you shouldn't be in this situation. That said:

type MyType1 = A | B of float 
type MyType2 = C | D of int 

// imagine this adds floats, and C -> A (=0.0) and D -> B
let DoIt x y =
    match x, y with
    | A, A -> 0.0
    | A, B z -> z
    | B z, A -> z
    | B z1, B z2 -> z1 + z2

let Convert x =
    match x with
    | C -> A
    | D i -> B (float i)

let Func (x:obj) (y:obj) =
    match x, y with
    | (:? MyType2 as xx), (:? MyType2 as yy) -> DoIt (Convert xx) (Convert yy)
    | (:? MyType1 as xx), (:? MyType2 as yy) -> DoIt xx (Convert yy)    
    | (:? MyType2 as xx), (:? MyType1 as yy) -> DoIt (Convert xx) yy
    | (:? MyType1 as xx), (:? MyType1 as yy) -> DoIt xx yy    
    | _ -> failwith "bad args"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文