解构被歧视的联盟

时间:2017-10-23 14:16:49

标签: f#

我有

形式的辨别联合类型
type ParameterName = string
type ParameterValues =
    | String of string[]
    | Float of float[]
    | Int of int[]
type Parameter = Parameter of ParameterName * ParameterValues

我想将ParameterValues部分传递给一个带泛型参数返回单元的函数,例如

let func1 (name:string) (data:'a) = printfn "%s" name

解构Parameter我可以像这样包裹func1

let func2 (Parameter (name, values)) =
    match values with
        | String s -> func1 name s
        | Float s -> func1 name s
        | Int s -> func1 name s

但是如果我必须为多个功能执行此操作,这将是不方便的。相反,我想定义一个更灵活的包装器,如下所示:

let func3 (fn: ('a -> 'b -> unit)) (Parameter (name, values)) =
    match values with
        | String s -> fn name s
        | Float s -> fn name s
        | Int s -> fn name s

然而这失败了,因为b的类型在匹配表达式的第一个选项中被限制为string[];因此匹配表达式失败,错误为Type string does not match type float.

这是预期的吗?我该如何解决这个问题?

2 个答案:

答案 0 :(得分:4)

这是预期的行为。问题是你不能直接将泛型函数作为参数传递给F#中的另一个函数。定义函数时如下:

let func3 (fn: ('a -> 'b -> unit)) (Parameter (name, values)) = (...)

...您正在定义一个泛型函数func3,它有两个通用参数,当指定它们时,可以使用给定的函数和参数调用它们。这可以写成:

\forall 'a, 'b . (('a -> 'b -> unit) -> Parameter -> unit)

您需要做的是使这些类型参数不是顶级参数,而是使第一个参数本身成为通用函数。你可以写成:

(\forall 'a, 'b . ('a -> 'b -> unit)) -> Parameter -> unit

这可以使用接口笨拙地用F#编写:

type IFunction<'a> =
  abstract Invoke<'b> : 'a -> 'b -> unit

let func1 = 
  { new IFunction<string> with
    member x.Invoke<'b> name (data:'b) = printfn "%s" name }

let func3 (fn: IFunction<string>) (Parameter (name, values)) =
    match values with
    | String s -> fn.Invoke name s
    | Float s -> fn.Invoke name s
    | Int s -> fn.Invoke name s

实际上,你的函数并不需要是通用的,因为你永远不会使用第二个参数 - 但是你可以通过将数据作为{{1你的代码比这个怪物要简单得多!

答案 1 :(得分:2)

根据Lee和Tomas的建议,我提出了以下解决方案:

type Parameter = Parameter of string * obj

let func0 (name:string) (data:obj) = printfn "%s %A" name data

let func1 (fn: string->obj->unit) (Parameter (name, value)) =
    fn name value

let p1 = Parameter ("p1", [|"a"; "b"|])
let p2 = Parameter ("p1", [|1.; 2.|])

func1 func0 p1
func1 func0 p2