【问题标题】:Deconstructing Discriminated Unions解构受歧视的工会
【发布时间】:2017-10-23 14:16:48
【问题描述】:

我有一个有区别的联合类型的表单

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.

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

【问题讨论】:

  • 是的,预计 F# 不支持更高级别的多态性。您可以将data 的类型更改为obj
  • 除了@Tomas 的回答,您还可以收到三倍相同的功能:let func3 fn1 fn2 fn3 (Parameter (name, values)) 否则有another compile-time solution

标签: f#


【解决方案1】:

这是预期的行为。问题是您不能直接将泛型函数作为参数传递给 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

实际上,您的函数实际上并不需要是通用的,因为您从不使用第二个参数 - 但您可以通过将数据传递为 obj 来实现使用此接口技巧可以实现的几乎任何东西你的代码会比这个怪物简单得多!

【讨论】:

  • 感谢您的快速回答。我对 F# 很陌生,所以这有点超出我的想象,但我想我理解了 obj 的想法。你能看看我的回答,告诉我你的想法是不是这样?
  • @sebhofer 是的,你的obj 实现就是我的想法! +1 对我的回答! (回答这类问题总是很棘手 - 来自 Haskell 的人会抱怨我的回答过于非技术性,而其他人会觉得这很有挑战性;很高兴你找到了适合你的解决方案!)
  • 我的评论当然不是批评,我喜欢你解释了一些背景。只是为了理解你的示例代码,我现在必须阅读接口,我觉得我应该先学习 F# 的其他方面。
  • 一个更快速的问题:我现在使用obj 理解解决方案的方式是它有效地禁用类型检查,因为一切都是obj(我不确定这是否是技术上的正确的说法,但我希望你明白要点)。它是否正确?如果是这样,这可能被认为是“黑客”,对吧?
  • @sebhofer 是的,这是正确的——如果你之后没有对 obj 值做任何不安全的事情,我不会说这是一个黑客行为——如果你只是想打印它,那很好,因为您可以安全地打印任何对象。如果你想做更多,那么这取决于究竟是什么......
【解决方案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

【讨论】:

  • 嗯,可以简化一点
  • let func0 = printfn "%s %A"
  • @FoggyFinder 好吧,如果不清楚,func0 并不是我真正将要使用的功能...
猜你喜欢
  • 2011-11-17
  • 2017-06-08
  • 1970-01-01
  • 2023-02-19
  • 2011-03-19
  • 2018-10-20
  • 1970-01-01
  • 2015-01-22
  • 1970-01-01
相关资源
最近更新 更多