将两个(或多个)选项值应用于F#中的函数的正确方法

时间:2017-04-16 16:37:47

标签: haskell f# functional-programming ocaml

最近,我发现了一种在功能世界中非常有用(和漂亮)的编程风格,称为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 valueNone

但是,这是我的问题,如果我们有一个“接受”两个(或多个)选项类型的函数,假设funcAB : 'a -> 'b -> 'c optionvalA : 'a optionvalB : '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中定义的运算符。

2 个答案:

答案 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运算符<*>