1

I need help understanding the concepts behind the following:

I have this:

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

But this doesn't work:

let getRequest = function
    | Success input -> input 
    | Failure msg -> msg

But this does:

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

Why does the initial "getRequest" fail?

Again, I just don't understand the basic rules for pattern matching. Can someone please shed some light on this?

The entire code is here:

module Core

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"};;

let getRequest = function
    | Success input -> input 
    | Failure msg -> msg

// Test
let result = Validate goodInput;;
let request = getRequest result;;
Scott Nimrod
  • 11,206
  • 11
  • 54
  • 118
  • What do you mean by "doesn't work"? The compiler certainly accepts your first definition of `getRequest`, but it constrains the success and failure types to be the same (because the return type needs to be well defined), so it can't be used with the output of `Validate`. – kvb Dec 04 '15 at 21:06
  • Is there some link or a book to figure out how pattern matching works with types? I really need some simplified answer that I'm able to digest. – Scott Nimrod Dec 04 '15 at 21:11
  • Start here: http://fsharpforfunandprofit.com – Fyodor Soikin Dec 04 '15 at 21:24

2 Answers2

3

Given this definition

let getRequest = function
| Success input -> input 
| Failure msg -> msg

we can reason about its type as follows:

  1. Since it's a function, it has type ?1 -> ?2 for some not-yet-known placeholder types.
  2. The input uses the Success and Failure union cases, so the input must itself actually be some type Result<?3,?4>, and the function's type is Result<?3,?4> -> ?2.
  3. The types of each branch must be the same as the function's return type.

    1. Looking at the first branch, we see this means that ?3 = ?2.
    2. Looking at the second branch, this means that ?4 = ?2.

    Therefore the overall type of the function must be Result<?2,?2> -> ?2, or using real F# notation, Result<'a,'a> -> 'a where 'a can be any type - this is a generic function definition.

But Validate has type Request -> Result<Request, string>, so its output isn't consistent with this definition of getResult because the success and failure types are different and you can't just directly pass the results from one to the other.

On the other hand, with

let getRequest = function
| Success input -> input 
| Failure msg -> failwithf "Invalid input: %s" msg

we would analyze it like this:

  1. As before, any function has type ?1 -> ?2.
  2. As before, the input must clearly be a Result<?3,?4>.
  3. But analyzing the branches, we get a different result:

    1. The first branch again leads to the constraint ?3 = ?2.
    2. But the second branch is different - failwithf with that format pattern takes a string and throws an exception (and can have any return type, since it never returns normally), so we see msg must be a string, so ?4 = string.

    So the overall type is Result<'a,string> -> 'a, which now allows you to pass a Result<Request,string> to it, as desired.

kvb
  • 54,864
  • 2
  • 91
  • 133
1

I think your question actually gives a good platform to explain pattern matching.

Let's start by thinking about the types involved, your getRequest function must be a function with some type signature getRequest : 'a -> 'b', i.e. it takes something of one type and returns something of another but let's look at what you've written.

let getRequest = 
    function
    | Success input -> input // this branch would return 'TSuccess
    | Failure msg -> msg // this brach would return 'TFailure

You can't have a function whose type signature is 'a -> 'b or 'c. (The only way to make this function compile is if 'b and 'c can be constrained to be the same type, then we do simply have a consistent 'a -> 'b).

But wait, enter the discriminated union.

Your discriminated union has type Result<'TSuccess,'TFailure>. Or, to describe things as I did above type 'd<'b,'c>. Now, we absolutely can have a function whose type signature is 'a -> 'd<'b,'c>.

'd<'b, 'c> is just a generic type which involves 'b and it involves 'c. The type signature includes all of these things, so hopefully you can see that we can contain both a 'TSuccess and 'TFailure in this type with no problem.

So, if we want to return something which can contain different combinations of types, we've found the perfect use case for the discriminated union. It can contain the 'TSuccess or the 'TFailure in one type. You can then choose between those results using pattern matching.

let getRequest = 
    function
    | Success input -> printfn "%A" input // do something with success case
    | Failure msg -> printfn "%A" msg // do something with failure case

So long as we have consistent return types in each case, we can insert any behaviour we want.

On to the next point of why throwing an exception is okay. Let's look at the type signature of the failwithf function: failwithf : StringFormat<'T,'Result> -> 'T. The failwithf function takes a string format and returns some type 'T.

So let's look at your second function again

let getRequest result = 
    match result with
    | Success input -> input // type 'TSuccess
    | Failure msg -> failwithf "Invalid input: %s" msg // type 'T inferred to be 'TSuccess

The function signature is consistent, 'a -> 'TSuccess.

TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
  • The function signature is maybe consistent but its lying. As you may throw an exception in there, it may then absolutely NOT return a 'TSuccess. The second part is that the whole point of Result<'TSuccess,'TFailure> is to remove exceptions. Not create them. – Helge Rene Urholm Dec 07 '15 at 07:16
  • @HelgeReneUrholm I was explaining why the above function compiles (since that was part of the question!) not making any kind of recommendation in favour of that code. – TheInnerLight Dec 07 '15 at 11:42
  • this is quite ok explained here: http://stackoverflow.com/a/34122575/5514938 A question may not necessarily be answered as a direct answer to the question. Especially not when the premisse of the questions is wrong, or the answer does not entirely cover those premisses. In short: Mu. Nimrod wants to learn programming. Not solving a specific (non-)issue. – Helge Rene Urholm Dec 07 '15 at 11:45