F#模式匹配元组的类型

时间:2010-02-06 18:50:16

标签: f# types pattern-matching tuples

我有一个curried函数,我希望它支持不同类型的参数,而不是继承关系:

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) = // ...

这就像模拟方法重载的行为一样,但是使用了curried函数。

4 个答案:

答案 0 :(得分:15)

您的代码不起作用,因为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.是否正确({ {1}},T1 * T1T1 * T2)?在这种情况下,你不能编写一个完全安全的curried函数,因为第二个参数的类型将“依赖”第一个参数的类型(如果第一个参数的类型为T2 * T2,那么第二个参数也是必须是T2(否则它也可以是T2)。

您可以编写一个安全的非curried函数,它接受以下类型的参数:

T1

该函数的类型为type MyArg = Comb1 of T1 * T1 | Comb2 of T1 * T2 | Comb3 of T2 * T2 。 如果你想要一个curried函数,你可以定义一个类型,允许你使用MyArg -> stringT1作为第一和第二个参数。

T2

然后,你的咖喱功能将是type MyArg = First of T1 | Second of T2 。但请注意,如果不允许一种参数类型组合(如果我理解正确,则不应允许MyArg -> MyArg -> string)。在这种情况下,你的函数只需抛出一个异常或类似的东西。

答案 1 :(得分:6)

基本上有三种不同的方法来实现这一目标。

首先是按照你的建议,通过向上转换为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库允许用户尝试使用此技术可视化任何类型的任何值,以便为不同(已知)类型(read more)调用自定义可视化例程。

第二种是用一种类型替换两种不同的类型:

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

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

type MyType = MyType1 of MyType1 | MyType2 of MyType2

答案 2 :(得分:3)

  

我有两个相似但不同的   类型。我可以很容易地转换一个   键入另一个。我想定义一个   将作用于的二进制操作   那些类型的实体,但我想   公开单个操作   客户端。

     

这个功能应该决定哪个   调用opXY,正确转发   类型。

这是正确答案不是“这就是你如何做”的情况之一,而是“不要那样做”。如果你不打算坚持使用它的习语和打字机,那么使用F#并没有什么优势。

从我的观点来看,如果你的类型非常相似,那么它们应该合并为相同的类型:

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

除此之外,您可以将两种类型换成另一种类型:

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

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

除此之外,请为您的不同类型公开两种不同的方法。

答案 3 :(得分:1)

这闻起来非常腥,你应该描述更大的问题背景,因为你似乎不应该处于这种情况。那说:

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"