首页 文章

F#模式匹配元组的类型

提问于
浏览
10

我有一个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'没有任何正确的子类型,不能用作类型测试或运行时强制的来源 .

这样做的优雅方式是什么?

EDIT :让我试着澄清一下 .

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

也就是说,而不是提供:

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 回答

  • 6

    你的代码没有't work, because F# generalizes the type of arguments to a type parameter. I think you can't动态测试类型 '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
    

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

    EDIT (2) 因此,从 T1T2 的所有4个双元素组合中,您需要可以采用的函数3.是否正确( T1 * T1T1 * T2T2 * T2 )?在这种情况下,你不能写一个完全安全的curried函数,因为第二个参数的类型将在第一个参数的类型上"depend"(如果第一个参数的类型为 T2 ,那么第二个参数也必须是 T2 (否则它也可以是 T1 )) .

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

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

    函数的类型是 MyArg -> string . 如果你想要一个curried函数,你可以定义一个类型,允许你使用 T1T2 作为第一个和第二个参数 .

    type MyArg = First of T1 | Second of T2
    

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

  • 1

    实现此目的主要有三种不同的方法 .

    第一种是按照你的建议通过向上转换来牺牲静态类型: 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
    
  • 15

    我有两种相似但不同的类型 . 我可以很容易地将一种类型转换为另一种类型我想定义一个二进制操作,它将作用于那些类型的实体,但我想向客户端公开一个操作 . 此函数应决定调用哪个opXY,正确转发类型 .

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

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

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

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

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

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

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

  • 3

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

    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"
    

相关问题