简体   繁体   中英

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.

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 eg

 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 eg

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM