0

How do I retrieve a value from a generic?

Specifically, I am attempting the following:

// Test
let result = Validate goodInput;;

// How to access record??
let request = getRequest result

Here's the code:

type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure

let bind nextFunction lastFunctionResult = 
    match lastFunctionResult with
    | Success input -> nextFunction input
    | Failure f -> Failure f

type Request = {name:string; email:string}

let validate1 input =
   if input.name = "" then Failure "Name must not be blank"
   else Success input

let validate2 input =
   if input.name.Length > 50 then Failure "Name must not be longer than 50 chars"
   else Success input

let validate3 input =
   if input.email = "" then Failure "Email must not be blank"   
   else Success input;;

let Validate = 
    validate1 
    >> bind validate2 
    >> bind validate3;;

// Setup
let goodInput = {name="Alice"; email="abc@abc.com"}
let badInput = {name=""; email="abc@abc.com"};;

// I have no clue how to do this...
let getRequest = function
    | "Alice", "abc@abc.com" -> {name="Scott"; email="xyz@xyz.com"}
    | _ -> {name="none"; email="none"}

// Test
let result = Validate goodInput;;

// How to access record??
let request = getRequest result
printfn "%A" result
Scott Nimrod
  • 11,206
  • 11
  • 54
  • 118

2 Answers2

2

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.

scrwtp
  • 13,437
  • 2
  • 26
  • 30
2

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.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736