Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to understand the Choice type in F#

I've been struggling to understand the code in the RoP article by Scott Wlaschin:

http://fsharpforfunandprofit.com/posts/railway-oriented-programming-carbonated/

He makes use of the Choice1Of2 & Choice2Of2 types in F#. I was trying to come to terms with how to utilize these things by debugging them, when I came across the the following scenario:

module TestModule
open Microsoft.VisualStudio.TestTools.UnitTesting

// generic union type (like Choice1Of2, I think)
type Things<'a> =
    | Thing of 'a

// explicit union type (for comparison)
type Numbers =   
    | Integer of int

[<TestClass>]
type Tests() =

    // method to make a Choice1Of2 (from the article)
    let makeChoice (a : string) : Choice<string, 'a> = 
        Choice1Of2 a

    [<TestMethod>]
    member public this.WhyYouNoHaveItemValueAndStuff() =      
        let choice1 = Thing "test"          // debug = Thing "this"
        let choice2 = Integer 3             // debug = Integer 3
        let choice3 = makeChoice "test"     // debug = Choice1Of2 w/Item = "test"
        let choice4 = Choice1Of2 "test"     // debug = Tests.choice4@24 ???

        // bogus test stuff below here
        let choices = (choice1, choice2, choice3, choice4)
        Assert.IsNotNull(choices)

Why is it that when I make a Choice1Of2 directly (choice4), do I not get the same debugging result as choice 3. Why is using a method to make choice3 neccessary to get the same kind of result as choice1 & 2?

EDIT:

It seems that changing choice4 to this:

let choice4 : Choice<string, Object> = Choice1Of2 "test" 

works it out. It's just totally unclear to me why I need that there. The right side of the assignment is as clear as day to what type is being set.

like image 677
TBone Avatar asked Jan 23 '15 17:01

TBone


Video Answer


1 Answers

The definition of the Choice type is as follows

 type Choice<'a, 'b> =
      | Choice1Of2 of 'a
      | Choice2Of2 of 'b

So when you construct an instance of the Choice type as you do in choice4 you only use one of these legs, this essentially leaves a hole (the type describing 'b) that the debugger has to fill, in fact at runtime it can't even be sure that the type is actually Choice<'a,'b> so you'll get some temporary type that is represented by an FSharpFunc. In much the same way the type inference mechanism would report Choice<string, 'a> where 'a represents the hole, until the instance is matched over which would then force you to type the other side. Providing the type signature inline e.g.

 let choice4 : Choice<string, bool> = Choice1Of2 "test"

Means you are filling the hole and providing the debugger enough information to represent the type correctly.

EDIT (see comments): choice3 is represented as Choice1Of2<string,obj> because obj is considered the Top (most general type). This is the type inference mechanisms fall back type that is used when it has exhausted all other options. If we add some code e.g.

let result =
    match choice3 with
    | Choice1Of2 t -> t.GetHashCode()
    | Choice2Of2 t -> t  

then we will get Choice<string, int> as the type of GetHashCode() is int thus the result of the second match clause must be int for the type of the result let expression to be consistent.

like image 99
Colin Bull Avatar answered Oct 01 '22 02:10

Colin Bull