D.F*_*F.F 6 f# pipeline idiomatic
我喜欢使用管道运算符'|>'.但是,当混合使用返回'Option-Typed-values'的函数返回'simple'值的函数时,事情会变得有点混乱,例如:
// foo: int -> int*int
// bar: int*int -> bool
let f (x: string) = x |> int |> foo |> bar
Run Code Online (Sandbox Code Playgroud)
工作,但它可能会抛出'System.FormatException:...'
现在假设我想通过使函数'int'给出一个可选的结果来解决这个问题:
let intOption x =
match System.Int32.TryParse x with
| (true, x) -> Some x
| (false,_) -> None
Run Code Online (Sandbox Code Playgroud)
现在唯一的问题当然是功能
let g x = x |> intOption |> foo |> bar
Run Code Online (Sandbox Code Playgroud)
由于输入错误,将无法编译.好的,只需定义一个'可选的'管道:
let ( |= ) x f =
match x with
| Some y -> Some (f y)
| None -> None
Run Code Online (Sandbox Code Playgroud)
现在我可以简单地定义:
let f x = x |> intOption |= foo |= bar
Run Code Online (Sandbox Code Playgroud)
一切都像魅力一样.
好的,问题:这是惯用的F#吗?是否可以接受?风格不好?
备注:当然,给定正确的类型,"| ="运算符允许随意拆分和合并"管道",只关注它们重要的选项:
x |> ...|> divisionOption |= (fun y -> y*y) |=...|>...
Run Code Online (Sandbox Code Playgroud)
其他答案尚未涵盖两个方面。
Option类型的单子运算代替像这样的成熟计算表达式MaybeBuilder(),我们可以定义让约束函数为该Option类型提供单子运算。让我们代表操作员的绑定操作>>=:
let (>>=) ma f = Option.bind f ma
// val ( >>= ) : ma:'a option -> f:('a -> 'b option) -> 'b option
let ``return`` = Some
// val return : arg0:'a -> 'a option
Run Code Online (Sandbox Code Playgroud)
从这跟随
let (>=>) f g a = f a >>= g
// val ( >=> ) : f:('a -> 'b option) -> g:('b -> 'c option) -> a:'a -> 'c option
let fmap f ma = ma >>= (``return`` << f)
// val fmap : f:('a -> 'b) -> ma:'a option -> 'b option
let join mma = mma >>= id
// val join : mma:'a option option -> 'a option
Run Code Online (Sandbox Code Playgroud)
fmap基本上是Opion.map; join将嵌套实例取消嵌套一级,由Kleisli运算符进行组合>=>是流水线的替代方法。
在轻量级语法中,运算符免于使用嵌套范围增加缩进。当将lambda函数串在一起时,这可能很有用,它允许嵌套同时最多缩进一层。
a_option
|> Option.bind (fun a ->
f a
|> Option.bind (fun b ->
g b
|> Option.bind ... ) )
Run Code Online (Sandbox Code Playgroud)
与
a_option
>>= fun a ->
f a
>>= fun b ->
g b
>>= ...
Run Code Online (Sandbox Code Playgroud)
使用(|>)似乎是一个非常突出的概念的实现,即通过计算链对值进行线程化。然而,由于 F# 运算符的语法限制(优先级和左/右关联性),在实际项目中使用此概念可能有些困难。即:
Option.mapor时Option.bind,就很难使用代码块。该代码只有在以下intOption |> Option.map foo |> Option.map bar情况下才能很好地工作:foobar是命名函数使用几个小函数,“链接”方法可以编写更简洁的代码。
\n注意:对于现实生活中的项目,我强烈建议咨询您的团队,因为新的运算符或扩展方法对于您团队的其他成员来说可能看起来违反直觉。
几乎是现实生活中的应用程序代码。假设您的应用程序使用命令行解析器来转换此命令行:
\nMyApp.exe -source foo -destination bar -loglevel debug\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 到Map<string, string>包含键/值对的 a 中。
现在,让我们loglevel只关注处理参数,看看代码是如何处理它的:
Map Key="loglevel"注意,可能有零个元素;enum类型LogLevel。注意,解析可能会失败;None有价值。让我们设置一些默认值;Some,所以只需调用Option.get。这是代码。注释指出了上面列表中的步骤:
\n\nMyApp.exe -source foo -destination bar -loglevel debug\nRun Code Online (Sandbox Code Playgroud)\n"loglevel"在这里,我们看到如何通过“可选”计算链顺序转换键 ( )。每个 lambda 都会为要转换的值引入自己的别名(例如strLogLevel)。
这是要使用的库:
\nlet logLevel =\n "loglevel"\n |> args.TryFind // (1)\n |> Option.bind ^<| Seq.tryPick Some // (2)\n |> Option.bind ^<| fun strLogLevel -> // (3)\n match System.Enum.TryParse(strLogLevel, true) with\n | true, v -> Some v\n | _ -> None\n |> Option.Or ^<| fun _ -> // (4)\n if System.Diagnostics.Debugger.IsAttached\n then Some LogLevel.Debug\n else None\n |> Option.OrDefault ^<| fun _ -> // (5)\n LogLevel.Verbose\n |> Option.get // (6)\nRun Code Online (Sandbox Code Playgroud)\n