F#使用匹配来验证参数

时间:2017-03-02 14:12:20

标签: validation parameters f# match

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

let foo = match bar with
| <test for valid> -> bar
| _ -> "invalid"

当然,由于类型不匹配而无效。所以我希望看到F#程序员用于此类事情的模式。比赛?如果/ THEN / ELSE?

还有别的吗?

4 个答案:

答案 0 :(得分:5)

你遇到了问题,因为你试图将一个值绑定到两种可能的类型,这取决于程序流程 - 这与静态类型不兼容。

如果我有一些值foo,则根据程序流程,它不能是stringint;它必须在编译时解析为一种类型。

但是,您可以使用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 'TNone。选项类型用于指示值的存在与否,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

<强>组合物

在这两种情况下,您都可以分别使用mapbind模块中关联的OptionResult函数轻松撰写选项或结果:

地图:

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