【问题标题】:Is there a way to make this continuation passing with codata example work in F#?有没有办法让这种延续传递与 codata 示例在 F# 中工作?
【发布时间】:2016-12-25 15:18:19
【问题描述】:
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 的错误消息对我来说非常清楚。我猜测是否可以使上述工作的问题的答案是否定的,没有直接将函数添加到数据类型中。但是我还不如使用接口,或者完全摆脱泛型参数。

编辑:马克的回复确实符合我的要求,但让我扩展这个问题,因为我没有充分解释它。我想要做的是用上面的技术来模仿post 中所做的事情。这样做的动机是避免内联函数,因为它们的可组合性很差 - 如果没有专门的泛型参数,它们就不能作为 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.

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

【问题讨论】:

  • 我假设您出于某种原因探索并拒绝了方法重载和活动模式,但很想知道原因。可组合性?类型推断?
  • 老实说,我现在真正需要的是 GADT,以便以类型安全的方式为我的库编写操作,而您在这里看到的只是获得所需多态性的失败尝试。我可以只用函数来完成它,我并不真的需要多态性……除非我可能需要它,而且我害怕提交,因为它会使库的结构过于僵化。另一方面,使用联合类型,由于模式匹配和简单的上下文相关执行,我将能够进行前瞻,但我将在 F# 中使用一种动态 DSL。
  • 没有隐含的多态性,那些inline 函数真的有点像 C 函数,因为它们只是位于顶部并且你可以调用它们,但是你不能在没有风险的情况下在 lambdas 中传递它们类型错误。在图书馆的过去迭代中,我也很愚蠢地使用了它们,并被它所吸引。我想我会尝试以动态方式对接子弹并使用 DU - 没有通用参数。
  • 另外,我认为不需要多说,但 DU 与函数具有双重性,因为它们只是省略了返回类型的函数,所以这就是多态性发挥作用的地方跟他们。我似乎每时每刻都有艰难的选择。

标签: f# continuation-passing


【解决方案1】:

移除类型注解:

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

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

  • sprintf 使用的格式说明符更改为更通用的内容。当您使用%s 时,您是说该占位符的参数必须是字符串,因此编译器会推断出xystring 值。
  • 添加 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

【讨论】:

  • 你的回复很好,因为我问了什么,但是阅读它让我意识到我没有解释这个问题是足够的深度。请查看编辑。
【解决方案2】:

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

也许,您可以使用 FSharp.Interop.Dynamic 来处理黑暗艺术,但您会浪费编译时间来检查哪种方式不符合要点。

【讨论】:

  • 我不认为文章中的内容是我试图在这里得到的那种多态性。刚才我尝试将示例直接翻译为 Haskell,就像在 F# 中一样,它在那里给出了相同的错误。我想我在这里要的是impredicative polymorphism。那个页面,就像很多 Haskell 的东西一样,对我来说读起来像是胡言乱语,但我需要在我的答案中制作程序可能就是这样。我怀疑它会很快发生。
【解决方案3】:

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

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# 的当前限制。如果语言是动态的,上面的代码段实际上会正确运行。

【讨论】:

    【解决方案4】:

    很遗憾,我并不完全清楚您要做什么。但是,似乎可以通过使用泛型方法创建接口来实现。例如,您可以通过以下方式获取来自your answer 的代码来工作:

    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
    

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

    【讨论】:

    • 问题在于,使用 DU(记录和接口也是如此)你不能真正写出像 Exp -> Exp -> ('a -> ' b -> 'c) -> Exp。原因是当你只有一个时,上面需要 3 个泛型类型参数。另一方面,这种签名在 GADT 和普通函数中是非常可能的——除了普通函数最后会有一个额外的返回参数。无论如何,尽管我在回复 s952163 时写了些什么,但我还是决定使用函数。我想出了如何以功能方式进行模式匹配......
    • ...对于由闭包表示的任何类型的数据结构(我将用于 NN 层)。它可以像解析器组合器一样完成。诀窍是为该结构上的模式匹配定义一个单独的 mini-DSL,并通过延续传递结果函数。对于我的用例来说,这实际上会更理想,因为对于每种层和操作,我不会到处都有冗余类型。它不会像常规模式匹配那样简洁,但比手动完成所有事情要好得多。
    猜你喜欢
    • 1970-01-01
    • 2015-09-20
    • 2012-07-06
    • 1970-01-01
    • 2020-03-09
    • 1970-01-01
    • 1970-01-01
    • 2014-11-15
    • 1970-01-01
    相关资源
    最近更新 更多