How do I retrieve a value from a composite generic type?

后端 未结 2 824
旧巷少年郎
旧巷少年郎 2021-01-27 14:47

How do I retrieve a value from a generic?

Specifically, I am attempting the following:

// Test
let result = Validate goodInput;;

// How to access record         


        
相关标签:
2条回答
  • 2021-01-27 15:17

    This seems to be a frequently asked question: How do I get the value out of a monadic value? The correct answer, I believe, is Mu.

    The monadic value is the value.

    It's like asking, how do I get the value out of a list of integers, like [1;3;3;7]?

    You don't; the list is the value.

    Perhaps, then, you'd argue that lists aren't Discriminated Unions; they have no mutually exclusive cases, like the above Result<'TSuccess,'TFailure>. Consider, instead, a tree:

    type Tree<'a> = Node of Tree<'a> list | Leaf of 'a
    

    This is another Discriminated Union. Examples include:

    let t1 = Leaf 42
    let t2 = Node [Node []; Node[Leaf 1; Leaf 3]; Node[Leaf 3; Leaf 7]]
    

    How do you get the value out of a tree? You don't; the tree is the value.

    Like 'a option in F#, the above Result<'TSuccess,'TFailure> type (really, it's the Either monad) is deceptive, because it seems like there should only be one value: the success. The failure we don't like to think about (just like we don't like to think about None).

    The type, however, doesn't work like that. The failure case is just as important as the success case. The Either monad is often used to model error handling, and the entire point of it is to have a type-safe way to deal with errors, instead of exceptions, which are nothing more than specialised, non-deterministic GOTO blocks.

    This is the reason the Result<'TSuccess,'TFailure> type comes with bind, map, and lots of other goodies.

    A monadic type is what Scott Wlaschin calls an 'elevated world'. While you work with the type, you're not supposed to pull data out of that world. Rather, you're supposed to elevate data and functions up to that world.

    Going back to the above code, imagine that given a valid Request value, you'd like to send an email to that address. Therefore, you write the following (impure) function:

    let send { name = name; email = email } =
        // Send email using name and email
    

    This function has the type Request -> unit. Notice that it's not elevated into the Either world. Still, you want to send the email if the request was valid, so you elevate the send method up to the Either world:

    let map f = bind (fun x -> Success (f x))
    let run = validate1 >> bind validate2 >> bind validate3 >> map send
    

    The run function has the type Request -> Result<unit,string>, so used with goodInput and badInput, the results are the following:

    > run goodInput;;
    val it : Result<unit,string> = Success unit
    > run badInput;;
    val it : Result<unit,string> = Failure "Name must not be blank"
    

    And then you probably ask: and how do I get the value out of that?

    The answer to that question depends entirely on what you want to do with the value, but, imagine that you want to report the result of run back to the user. Displaying something to the user often involves some text, and you can easily convert a result to a string:

    let reportOnRun = function
        | Success () -> "Email was sent."
        | Failure msg -> msg
    

    This function has the type Result<unit,string> -> string, so you can use it to report on any result:

    > run goodInput |> reportOnRun;;
    val it : string = "Email was sent."
    > run badInput |> reportOnRun;;
    val it : string = "Name must not be blank"
    

    In all cases, you get back a string that you can display to the user.

    0 讨论(0)
  • 2021-01-27 15:25

    You mean how do you extract the record out of your result type? Through pattern matching, that's what you're already doing in bind.

    let getRequest result = 
        match result with
        | Success input -> input 
        | Failure msg -> failwithf "Invalid input: %s" msg
    
    let result = Validate goodInput
    let record = getRequest result
    

    This will return the record or throw an exception. Up to you how you handle the success and failure cases once you have your Result - that could be throwing an exception, or turning it into option, or logging the message and returning a default etc.

    0 讨论(0)
提交回复
热议问题