用F#做鱼

kae*_*fer 5 f#

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

在更广泛的F#社区中这种做法的证据可以在Scott Wlaschin的铁路导向编程(第2部分)中找到,作为返回Result<'TSuccess,'TFailure>类型的函数的组合.

推理哪里有绑定,必须还有鱼,我尝试let (>=>) f g a = f a >>= g用绑定函数本身参数化规范的Kleisli运算符的定义:

let mkFish bind f g a = bind g (f a)
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

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

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

Gus*_*Gus 5

如果没有“高级种类”,您将无法自然地做到这一点。

鱼的签名应类似于:

let (>=>) (f:'T -> #Monad<'U>``) (g:' U -> #Monad<'V>) (x:'T) : #Monad<'V> = bind (f x) g
Run Code Online (Sandbox Code Playgroud)

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

话虽如此,如果您真的想使用泛型fish运算符,则可以使用F#+,它已经通过使用静态约束进行了定义。如果在这里查看第5个代码示例,您将看到它在不同类型上的实际作用。

当然,您也可以定义自己的代码,但是要编写很多东西,以便使其在大多数常见情况下都能正常工作。您可以从库中获取代码,或者如果需要的话,我可以编写一个小的(但有限的)代码示例。

这行定义了普通鱼。

我认为一般来说,使用运算符时确实会感到缺乏通用功能,因为如您所知,您需要打开和关闭模块。并不是像用模块名称为函数添加前缀的函数那样,您也可以使用运算符(例如Option.(>=>))来完成这些功能,但是这违背了使用运算符的全部目的,我的意思是它不再是运算符。