最近,我发现了一种在功能世界中非常有用(和漂亮)的编程风格,称为Railway oriented programming。 例如,当想要创建生成选项类型的函数管道时,如果有任何失败,我们想要返回None,我们可以这样做:
someOptionValue // : 'a option
>>= func1 // func1: 'a -> 'b option
>>= func2 // func2: 'b -> 'c option
and so on...
其中(>>=) : 'a option -> (a' -> 'b option) -> 'b option
运算符会将值应用于左侧,如果它是Some value
或None
。
但是,这是我的问题,如果我们有一个“接受”两个(或多个)选项类型的函数,假设funcAB : 'a -> 'b -> 'c option
,valA : 'a option
和valB : 'b option
,我们仍然会怎样想要创建这个管道或使用一些不错的运算符(不是专门为此创建一个新的运算符,而是使用一些标准方法,特别是我不想使用match ... with
来“解包”选项值)
目前我有这样的事情:
valA
>>= (funcAB >> Some)
>>= (fun ctr -> valB >>= ctr)
但似乎并不“正确”(或者有趣的是更好的词;]),如果函数需要更多参数或者我们想要创建更长的管道,它就无法很好地扩展。有更好的方法吗?
我使用过F#语法,但我认为这个问题可以应用于任何函数式编程语言,如OCaml和Haskell。
编辑(解决方案):
感谢chi的回答,我创建了以下代码F#,这比我以前的代码更加惯用:
funcAB <!> valA <*> valB |> Option.flatten
如果我们有更多的值,那么它看起来很好:funcAB <!> valA <*> valB <*> valC <*> ...
。
我使用了YoLo中定义的运算符。
答案 0 :(得分:5)
在Haskell中,我们可以使用Applicative
语法:
如果
valA :: f a
valB :: f b
funAB :: a -> b -> f c
然后
join $ funAB <$> valA <*> valB :: f c
提供f
是monad(如Maybe
,Haskell的option
)。
我想,只要您定义运算符
,它也应该适用于F#(<$>) :: (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
join :: f (f a) -> f a
上述技巧是穷人的Idris !-notation (bang notation)版本。
另一个常见选项是使用do
do a <- valA
b <- valB
funAB a b
但这与使用>>=
相当,确实如此:
valA >>= \a ->
valB >>= \b ->
funAB a b
并不复杂。
答案 1 :(得分:2)
一种选择是使用computation expressions。对于option
,没有标准的,但您可以轻松创建自己的:
type OptionBuilder() =
member this.Bind (x, f) = Option.bind f x
member this.Return x = Some x
let optional = OptionBuilder()
let a, b = Some(42), Some(7)
let f x y = x + y
let res = optional {
let! x = a
let! y = b
return f x y
}
非常类似于Haskells do
表示法。
有关更高级的功能,请查看F#+,其中还包含一个通用的applicative functor运算符<*>
。