用F#做鱼

时间:2017-03-15 17:23:30

标签: f#

Kleisli组合运算符>=>,也称为Haskell圈中的“鱼”,在需要组合专用函数的许多情况下可能会派上用场。它的工作方式类似于>>运算符,但它不是组合简单函数'a -> 'b,而是赋予它们一些特殊属性,最好用'a -> m<'b>表示,其中m是类似monad的类型或函数返回值的某些属性。

可以找到更广泛的F#社区中这种做法的证据,例如在Scott Wlaschin的Railway oriented programming (part 2)作为返回Result<'TSuccess,'TFailure>类型的函数组合。

推测在有绑定的地方,必须还有fish,我尝试使用绑定函数本身对规范的Kleisli运算符的定义let (>=>) f g a = f a >>= g进行参数化:

let mkFish bind f g a = bind g (f a)

这非常有用,但通常不应该在面向用户的代码上释放特殊操作符。我可以编写返回选项的函数......

module Option =
    let (>=>) f = mkFish Option.bind f
    let odd i = if i % 2 = 0 then None else Some i
    let small i = if abs i > 10 then None else Some i
    [0; -1; 9; -99] |> List.choose (odd >=> small)
    // val it : int list = [-1; 9]

...或者我可以将函数应用程序设计到​​堆栈的两个最高值并将结果推回去,而不必引用我明确操作的数据结构:

module Stack =
    let (>=>) f = mkFish (<||) f
    type 'a Stack = Stack of 'a list
    let pop = function
    | Stack[] -> failwith "Empty Stack"
    | Stack(x::xs) -> x, Stack xs
    let push x (Stack xs) = Stack(x::xs)
    let apply2 f =
        pop >=> fun x ->
        pop >=> fun y ->
        push (f x y)

但困扰我的是签名val mkFish : bind:('a -> 'b -> 'c) -> f:('d -> 'b) -> g:'a -> a:'d -> 'c毫无意义。类型变量处于混乱的顺序,它过于笼统('a应该是一个函数),而且我没有看到一种自然的注释方式。

如果没有正式的仿函数和monad,我怎么能在这里抽象,而不必为每种类型明确定义Kleisli运算符?

1 个答案:

答案 0 :(得分:4)

如果没有高级种类,你不能以自然的方式做到这一点。

鱼的签名应该是这样的:

let (>=>) (f:'T -> #Monad<'U>``) (g:' U -> #Monad<'V>) (x:'T) : #Monad<'V> = bind (f x) g

在当前的.NET类型系统中无法代表。

话虽如此,如果你真的想使用泛型鱼类运算符,你可以使用已经使用静态约束定义它的F#+。如果你看第5 code sample here,你会看到它在不同类型的行动中。

当然你也可以定义自己的,但是要编写很多东西,以便在大多数常见场景中使其表现正常。您可以从库中获取代码,或者如果您愿意,我可以编写一个小的(但有限的)代码示例。

通用鱼在this line中定义。

我认为通常你在使用运算符时确实感觉缺乏泛型函数,因为正如你所发现的,你需要打开和关闭模块。它不像你用模块名称作为前缀的函数,你也可以用运算符(类似Option.(>=>))这样做,但是它失败了使用运算符的全部目的,我的意思是&# 39; s不再是运营商。