F#使用匹配来验证参数

use*_*098 2 validation parameters f# match

我正在学习F#.我想知道验证输入参数的最佳实践.在我的天真中,我以为我可以这样做:

let foo = match bar with
| <test for valid> -> bar
| _ -> "invalid"
Run Code Online (Sandbox Code Playgroud)

当然,由于不匹配的类型,这不起作用.所以我希望看到F#程序员用于此类事情的模式.比赛?如果/然后/别的吗?

别的什么?

The*_*ght 6

您遇到问题是因为您尝试将值绑定到可能是两种可能类型的某些类型,具体取决于程序流 - 这与静态类型不兼容.

如果我有一些价值foo,它不能是,例如,一个stringOR,int取决于程序流程; 它必须在编译时解析为一种类型.

但是,您可以使用可以在单个类型中表示多个不同选项的区别联合.

以下是完成此操作的方法的摘要.


结果类型/要么

F#4.1,目前可通过nuget获得,介绍了这种Result类型.您可能会发现此类型Either在其他语言中被称为.

它的定义如下:

[<Struct>] 
type Result<'T,'TError> =  
    /// Represents an OK or a Successful result. The code succeeded with a value of 'T. 
    | Ok of ResultValue:'T  
    /// Represents an Error or a Failure. The code failed with a value of 'TError representing what went wrong. 
    | Error of ErrorValue:'TError
Run Code Online (Sandbox Code Playgroud)

如果你是F#4.1之前(非常有可能).您可以自己定义此类型,但必须删除该[<Struct>]属性.

然后你可以创建一个tryParseFloat函数:

let tryParseFloat str =
   match System.Double.TryParse str with
   |  true, f -> Ok f
   | _ -> Error <| sprintf "Supplied string (%s) is not a valid float" str
Run Code Online (Sandbox Code Playgroud)

您可以确定成功或失败:

match tryParseFloat "0.0001" with
|Ok v -> // handle success
|Error err -> // handle error
Run Code Online (Sandbox Code Playgroud)

在我看来,这是首选方案,特别是在内置类型的F#4.1+中.这是因为它允许您包含有关某些活动失败的方式和原因的信息.


选项类型/可能

option类型包含Some 'T或简单None.选项类型用于指示值的存在或不存在,None填充类似于null其他语言的角色,尽管更安全.

您可能会发现此类型Maybe在其他语言中被称为.

let tryParseFloat str =
   match System.Double.TryParse str with
   |  true, f -> Some f
   | _ -> None
Run Code Online (Sandbox Code Playgroud)

您可以确定成功或失败:

match tryParseFloat "0.0001" with
|Some value -> // handle success
|None -> // handle error
Run Code Online (Sandbox Code Playgroud)

组成

在这两种情况下,您可以分别使用和模块中的关联mapbind函数来组合选项或结果:OptionResult

地图:

val map: mapping:('T -> 'U) -> option:'T option -> 'U option   
val map : mapping:('T -> 'U) -> result:Result<'T, 'TError> -> Result<'U, 'TError>
Run Code Online (Sandbox Code Playgroud)

map功能允许您从中获取普通功能,'a -> 'b并使其对结果或选项进行操作.

用例:将结果与始终成功的函数组合并返回新结果.

tryParseFloat "0.001" |> Result.map (fun x -> x + 1.0);;
val it : Result<float,string> = Ok 1.001
Run Code Online (Sandbox Code Playgroud)

绑定:

val bind: binder:('T -> 'U option) -> option:'T option -> 'U option
val bind: binder:('T -> Result<'U, 'TError>) -> result:Result<'T, 'TError> -> Result<'U, 'TError>
Run Code Online (Sandbox Code Playgroud)

bind功能允许您将结果或选项与接受输入并生成结果或选项的函数组合

用例:将结果与可能成功或失败的另一个函数组合并返回新结果.

例:

let trySqrt x =
   if x < 0.0 then Error "sqrt of negative number is imaginary"
   else Ok (sqrt x)
Run Code Online (Sandbox Code Playgroud)
tryParseFloat "0.001" |> Result.bind (fun x -> trySqrt x);;
val it : Result<float,string> = Ok 0.0316227766

tryParseFloat "-10.0" |> Result.bind (fun x -> trySqrt x);;
val it : Result<float,string> = Error "sqrt of negative number is imaginary"

tryParseFloat "Picard's Flute" |> Result.bind (fun x -> trySqrt x);;
val it : Result<float,string> =
  Error "Supplied string (Picard's Flute) is not a valid float"
Run Code Online (Sandbox Code Playgroud)

请注意,在这两种情况下,我们返回单个结果或选项,尽管链接了多个操作 - 这意味着通过遵循这些模式,您只需在完成所有验证后检查一次结果.

这避免了嵌套if语句或match语句的潜在可读性噩梦.

阅读更多相关信息的好地方是之前提到过的铁路定向编程文章.

例外

最后,您可以选择抛出异常作为阻止某些值进行验证的方法.如果你期望它发生,这绝对不是首选,但如果事件真的特殊,这可能是最好的选择.