Cygnus X-1
Cygnus X-1

Reputation: 53

using a function inside a discriminated union

I would like to do some unit tests on a function that accepts a DU and returns another :

type Commands = 
  | Schedule of string
  | Abandon of string

type Events =
  | Scheduled of string
  | Abandoned of string

the function is the following :

let exec state = function
    | Schedule (cmd) -> Choice1Of2( Scheduled("yes")) 
    | Abandon(cmd)  -> Choice1Of2( Abandoned ("no")) 

My tests are as follows :

let result:Choice<Events,string> = exec "initial state" <| Schedule("myCommand");;

result has the following type Choice<Events,string>, I would have loved to get some quick function in order to use them like this :

assertEvent Scheduled (fun e -> Assert.Equal("should produce GameScheduled Event",gameScheduled, e)) result

But to do that I would have the following home made assert function :

let assertEvent<'TEvent> f g result =
    match result  with
        | Choice1Of2(e) -> 
            match e with 
            | f(evt) ->  g(evt)
            | _ -> Assert.None("event not recognised",Some(e)) 
        | Choice2Of2(reason) -> Assert.None("reason",Some(reason))

I was expecting the function f to allow pattern matching on the fly but it does not. Instead I have the following error :

The pattern disciminator 'f' is not defined

Am I doing somthing wrong somewhere ? my fsharp skills are not that high...

Upvotes: 4

Views: 113

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233150

A normal function like f can't be used as a pattern discriminator, but you can pass Active Patterns around as arguments:

let assertEvent<'TEvent> (|F|_|) g result =
    match result  with
        | Choice1Of2(e) -> 
            match e with 
            | F(evt) ->  g(evt)
            | _ -> Assert.None("event not recognised",Some(e)) 
        | Choice2Of2(reason) -> Assert.None("reason",Some(reason))

This does, however, require you to also pass an Active Pattern as an argument, which is a bit cumbersome:

assertEvent
    (function Scheduled(x) -> Some x | _ -> None)
    (fun e -> Assert.Equal("should produce GameScheduled Event",gameScheduled, e))
    result

This isn't the way I'd approach the problem, though. What I'd prefer is to write a boolean expression that attempts to pull out and compare the values that I want to verify.

For starters, you could create a little generic helper function to pull out one of the choices from Choice<'a, 'b>:

let toOption1 = function Choice1Of2 x -> Some x | _ -> None

This function has the type Choice<'a,'b> -> 'a option. (I'll leave it as an exercise to define an equivalent toOption2 function.)

Now you can define a boolean expression that pulls out the data if it's there, and compares it with an expected value:

result
|> toOption1
|> Option.map (function Scheduled x -> x | _ -> "")
|> Option.exists ((=) expected)

This is a boolean expression, so you can use Unquote to turn it into an assertion. This is similar to this approach that I've previously described.

Upvotes: 4

Related Questions