在F#中制作鱼(Make Fish in F#)

编程入门 行业动态 更新时间:2024-10-24 14:28:14
在F#中制作鱼(Make Fish in F#)

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

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

推理说有一个绑定的地方,也必须有鱼,我尝试使用绑定函数本身对规范的Kleisli操作符的定义let (>=>) fga = fa >>= 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应该是一个函数),我没有看到一种自然的方式来注释它。

在没有正式函子和单子的情况下,我怎么能在这里抽象出来,而不必为每种类型明确定义Kleisli算子?

The Kleisli composition operator >=>, also known as the "fish" in Haskell circles, may come in handy in many situations where composition of specialized functions is needed. It works kind of like the >> operator, but instead of composing simple functions 'a -> 'b it confers some special properties on them possibly best expressed as 'a -> m<'b>, where m is either a monad-like type or some property of the function's return value.

Evidence of this practice in the wider F# community can be found e.g. in Scott Wlaschin's Railway oriented programming (part 2) as composition of functions returning the Result<'TSuccess,'TFailure> type.

Reasoning that where there's a bind, there must be also fish, I try to parametrize the canonical Kleisli operator's definition let (>=>) f g a = f a >>= g with the bind function itself:

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

This works wonderfully with the caveat that generally one shouldn't unleash special operators on user-facing code. I can compose functions returning options...

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]

... or I can devise a function application to the two topmost values of a stack and push the result back without having to reference the data structure I'm operating on explicitly:

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)

But what bothers me is that the signature val mkFish : bind:('a -> 'b -> 'c) -> f:('d -> 'b) -> g:'a -> a:'d -> 'c makes no sense. Type variables are in confusing order, it's overly general ('a should be a function), and I'm not seeing a natural way to annotate it.

How can I abstract here in the absence of formal functors and monads, not having to define the Kleisli operator explicitly for each type?

最满意答案

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

鱼的签名应该是这样的:

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

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

话虽如此,如果你真的想使用通用的鱼类操作符,你可以使用F#+ ,它已经通过使用静态约束来定义它。 如果您在这里查看第五个代码示例,您将看到它在不同类型的操作中。

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

通用鱼在这一行中定义。

我认为一般来说,当你使用操作符时,你真的感觉到缺乏泛型函数,因为正如你发现的那样,你需要打开和关闭模块。 它不像函数那样以模块名称作为前缀,你也可以使用运算符(类似于Option.(>=>) ),但它违背了使用运算符的全部目的,我的意思是它不再是运营商。

You can't do it in a natural way without Higher Kinds.

The signature of fish should be something like:

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

which is unrepresentable in current .NET type system.

Having said that, if you really want to use a generic fish operator you can use F#+ which has it already defined by using static constraints. If you look at the 5th code sample here you will see it in action over different types.

Of course you can also define your own, but there is a lot of things to code, in order to make it behave properly in most common scenarios. You can grab the code from the library or if you want I can write a small (but limited) code sample.

The generic fish is defined in this line.

I think in general you really feel the lack of generic functions when using operators, because as you discovered, you need to open and close modules. It's not like functions that you prefix them with the module name, you can do that with operators as well (something like Option.(>=>)) , but then it defeats the whole purpose of using operators, I mean it's no longer an operator.

更多推荐

本文发布于:2023-07-28 18:44:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1307989.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Fish

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!