4
type Interpreter<'a> =
| RegularInterpreter of (int -> 'a)
| StringInterpreter of (string -> 'a)

let add<'a> (x: 'a) (y: 'a) (in_: Interpreter<'a>): 'a = 
    match in_ with
    | RegularInterpreter r -> 
        x+y |> r
    | StringInterpreter r -> 
        sprintf "(%s + %s)" x y |> r

它无法'a在编译时解决的错误消息对我来说非常清楚。我猜测是否可以使上述工作的问题的答案是否定的,没有直接将函数添加到数据类型中。但那我不妨使用一个接口,或者完全摆脱泛型参数。

编辑:马克的回复确实符合我的要求,但让我扩展这个问题,因为我没有充分解释它。我想要做的是使用上面的技术来模仿这篇文章中所做的事情。这样做的动机是避免内联函数,因为它们的可组合性很差——如果没有专门的通用参数,它们就不能作为 lambdas 传递。

我希望我可以通过将带有通用参数的联合类型传递给闭包来解决它,但是......

type Interpreter<'a> =
| RegularInterpreter of (int -> 'a)
| StringInterpreter of (string -> 'a)

let val_ x in_ =
    match in_ with
    | RegularInterpreter r -> r x
    | StringInterpreter r -> r (string x)

let inline add x y in_ = 
    match in_ with
    | RegularInterpreter r -> 
        x in_ + y in_ |> r
    | StringInterpreter r -> 
        sprintf "(%A + %A)" (x in_) (y in_) |> r

let inline mult x y in_ = 
    match in_ with
    | RegularInterpreter r -> 
        x in_ * y in_ |> r
    | StringInterpreter r -> 
        sprintf "(%A * %A)" (x in_) (y in_) |> r

let inline r2 in_ = add (val_ 1) (val_ 3) in_

r2 (RegularInterpreter id)
r2 (StringInterpreter id) // Type error.

最后一行给出了一个类型错误。有没有解决的办法?尽管由于它们对可组合性的限制,我更希望函数不要被内联。

4

4 回答 4

4

删除类型注释:

let inline add x y in_ = 
    match in_ with
    | RegularInterpreter r -> 
        x + y |> r
    | StringInterpreter r -> 
        sprintf "(%A + %A)" x y |> r

您还需要进行一些其他更改,我也在上面合并了这些更改:

  • 将使用的格式说明符更改为sprintf更通用的内容。当您使用 时%s,您是说该占位符的参数必须是一个字符串,因此编译器会推断xy成为string值。
  • 添加inline关键字。

通过这些更改,推断的类型add现在是:

x: ^a -> y: ^b -> in_:Interpreter<'c> -> 'c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b -> int)

您会注意到它适用于任何+定义为将输入参数转换为int. 在实践中,这可能只意味着int它本身,除非您定义自定义运算符。

FSI 烟雾测试:

> add 3 2 (RegularInterpreter id);;
val it : int = 5
> add 2 3 (StringInterpreter (fun _ -> 42));;
val it : int = 42
于 2016-12-25T16:29:11.107 回答
3

编译器最终默认为 int,而您想要的多态性在 F# 中很难实现。这篇文章阐明了这一点。

也许,您可以使用黑暗艺术来工作,FSharp.Interop.Dynamic但是您会浪费编译时间来检查哪种方式失败了。

于 2016-12-26T13:30:18.380 回答
2

我得出的结论是,我正在尝试做的事情是不可能的。我有一种预感,它已经是,但证据如下:

let vale (x,_,_) = x
let adde (_,x,_) = x
let multe (_,_,x) = x

let val_ x d =
    let f = vale d
    f x

let add x y d =
    let f = adde d
    f (x d) (y d)

let mult x y d =
    let f = multe d
    f (x d) (y d)

let in_1 =
    let val_ (x: int) = x
    let add x y = x+y
    let mult x y = x*y
    val_,add,mult

let in_2 =
    let val_ (x: int) = string x
    let add x y = sprintf "(%s + %s)" x y
    let mult x y = sprintf "(%s * %s)" x y
    val_,add,mult

let r2 d = add (val_ 1) (val_ 3) d

//let test x = x in_1, x in_2 // Type error.

let a2 = r2 in_1 // Works
let b2 = r2 in_2 // Works

推理是,如果不能使用作为参数传递的普通函数来完成,那么使用接口、记录、可区分联合或任何其他方案肯定是不可能的。标准函数比上述任何函数都更通用,如果它们不能做到,那么这是语言的基本限制。

使代码不通用的不是缺少 HKT,而是像这样简单。事实上,通过 Reddit 帖子中链接到的 Final Tagless 论文,Haskell 也存在同样的问题,即需要复制解释器而不使用谓词类型扩展——尽管我环顾四周,似乎将来会删除谓词类型因为扩展很难维护。

尽管如此,我确实希望这只是 F# 的当前限制。如果语言是动态的,那么上面的代码段实际上会正确运行。

于 2016-12-26T10:36:59.997 回答
2

不幸的是,我并不完全清楚您要做什么。但是,似乎可以通过使用泛型方法创建接口来实现。例如,您可以通过以下方式从您的答案中获取代码以使其工作:

type I = abstract Apply : ((int -> 'a) * ('a -> 'a -> 'a) * ('a -> 'a -> 'a)) -> 'a

//let test x = x in_1, x in_2 // Type error.
let test (i:I) = i.Apply in_1, i.Apply in_2

let r2' = { new I with member __.Apply d = add (val_ 1) (val_ 3) d }
test r2' // no problem

如果你想通用地使用一个值(例如一个函数输入),那么在大多数情况下,最简洁的方法是创建一个具有通用方法的接口,其签名表达了所需的多态性。

于 2016-12-28T21:10:17.860 回答