我正在学习F#。我想知道验证输入参数的最佳实践。在我的天真中,我以为我可以做这样的事情:
let foo = match bar with
| <test for valid> -> bar
| _ -> "invalid"
当然,由于类型不匹配而无效。所以我希望看到F#程序员用于此类事情的模式。比赛?如果/ THEN / ELSE?
还有别的吗?
答案 0 :(得分:5)
你遇到了问题,因为你试图将一个值绑定到两种可能的类型,这取决于程序流程 - 这与静态类型不兼容。
如果我有一些值foo
,则根据程序流程,它不能是string
或int
;它必须在编译时解析为一种类型。
但是,您可以使用discriminated union来表示单个类型中的多个不同选项。
以下是完成此操作的方法的摘要。
结果类型/ <
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
如果你是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
您可以确定成功或失败:
match tryParseFloat "0.0001" with
|Ok v -> // handle success
|Error err -> // handle error
在我看来,这是首选方案,特别是在内置类型的F#4.1+中。这是因为它允许您包含有关某些活动失败的方式和原因的信息。
选项类型/可能
option
类型包含Some 'T
或None
。选项类型用于指示值的存在与否,None
在其他语言中填充与null
类似的角色,尽管更安全。
您可能会在其他语言中找到此类型称为Maybe
。
let tryParseFloat str =
match System.Double.TryParse str with
| true, f -> Some f
| _ -> None
您可以确定成功或失败:
match tryParseFloat "0.0001" with
|Some value -> // handle success
|None -> // handle error
<强>组合物强>
在这两种情况下,您都可以分别使用map
和bind
模块中关联的Option
和Result
函数轻松撰写选项或结果:
地图:
val map: mapping:('T -> 'U) -> option:'T option -> 'U option
val map : mapping:('T -> 'U) -> result:Result<'T, 'TError> -> Result<'U, 'TError>
map
功能可让您从'a -> 'b
获取普通功能,并使其对结果或选项进行操作。
用例:将结果与始终成功的函数组合并返回新结果。
tryParseFloat "0.001" |> Result.map (fun x -> x + 1.0);; val it : Result<float,string> = Ok 1.001
绑定:
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>
bind
函数允许您将结果或选项与接受输入并生成结果或选项的函数组合
用例:将结果与可能成功或失败的另一个函数组合并返回新结果。
示例:
let trySqrt x =
if x < 0.0 then Error "sqrt of negative number is imaginary"
else Ok (sqrt x)
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"
请注意,在这两种情况下,我们返回单个结果或选项,尽管链接了多个操作 - 这意味着通过遵循这些模式,您只需在完成所有验证后检查一次结果。
这避免了嵌套if
语句或match
语句的潜在可读性噩梦。
阅读更多相关信息的好地方是之前提到的Railway Oriented Programming文章。
<强>例外强>
最后,您可以选择抛出异常作为阻止某些值进行验证的方法。如果你期望它发生,这绝对不是首选,但如果事件真的特殊,这可能是最好的选择。
答案 1 :(得分:4)
在F#中表示无效状态的基本方法是使用option
类型,它有两个可能的值。 None
表示无效状态,Some(<v>)
表示有效值<v>
。
所以在你的情况下,你可以写一些类似的东西:
let foo =
match bar with
| <test for valid> -> Some(bar)
| _ -> None
如果match
是实际模式(例如空列表或特定的无效数字或<test for valid>
值),null
构造效果很好,但如果它只是一个布尔表达式,那么使用if
编写条件可能更好:
let foo =
if <test for valid> bar then Some(bar)
else None
答案 2 :(得分:1)
你可以沿着这条路做点什么
type Bar =
| Bar of string
| Foo of int
let (|IsValidStr|_|) x = if x = Bar "bar" then Some x else None
let (|IsValidInt|_|) x = if x = Foo 0 then Some x else None
let foo (bar:Bar) =
match bar with
| IsValidStr x -> Some x
| IsValidInt x -> Some x
| _ -> None
那就是你可以使用活动模式来检查实际的业务规则并返回一个Option实例
答案 3 :(得分:1)
根据OP在评论中写的内容:
您可以在Fyodor linked的帖子中定义一个类型,它可以捕获您的两种可能结果:
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
您的验证码变为:
let checkBool str =
match bool.TryParse str with
| true, b -> Success b
| _ -> Failure ("I can't parse this: " + str)
使用时,再次使用匹配:
let myInput = "NotABool"
match checkBool myInput with
| Success b -> printfn "I'm happy: %O" b
| Failure f -> printfn "Did not like because: %s" f
如果您只想继续使用有效的bool,那么您的代码只能在无效参数上失败,所以您可以这样做:
let myValidBool =
match checkBool myInput with
| Success b -> b
| Failure f -> failwithf "I did not like the args because: %s" f