TysonMN / tysonmn.github.io

Blog of Tyson Williams about software engineering and functional programming
https://tysonwilliams.coding.blog/
MIT License
3 stars 7 forks source link

2020-09-21_bottom_type_in_fsharp #17

Closed utterances-bot closed 3 years ago

utterances-bot commented 3 years ago

Bottom Type in F

https://tysonwilliams.coding.blog/2020-09-21_bottom_type_in_fsharp

tpetricek commented 3 years ago

It turns out that you can actually create a value of your Bottom type using let rec:

type Bottom = private Bottom of Bottom

let rec b = Bottom b

match b with 
| Bottom b' -> printfn "Yo!"

There is even a paper from the old days of F# (2006) explaining how let rec works on values: Initializing Mutually Referential Abstract Objects: The Value Recursion Challenge

TysonMN commented 3 years ago

I had considered the possibility that such a value could have a reference to itself, but I thought that it would require mutation to create. Thanks for tell me about this :)

abelbraaksma commented 3 years ago

Since you posted this, we've had a nice discussion in the language suggestion. Conclusion (from my pov): the CLR does not allow a type to be at the bottom of the inheritance chain, which disallows a Bottom type.

The two main properties ((1) cannot be instantiated, (2) sits at the bottom of the inheritance chain) can only be approximated, copied from that thread:

  1. A type that cannot be instantiated

    type Bottom<'T when 'T : not struct and 'T : unmanaged> =
        | Bottom of 'T
    
        static member create() = Bottom(Unchecked.defaultof<_>)

    Using Bottom.create() will, however, throw FS1202, because the ambiguity cannot be resolved.

  2. A normal non-instantiable type

    type Bottom =
        // will return null
        static member create() = Unchecked.defaultof<Bottom>

    You cannot create Bottom the normal way as it does not have public constructors. But this type can be used in normal code as it resolves just fine. However, property (2) cannot be resolved here, or anywhere else, as "a type that is a derived type from every other type" cannot exist in CLR.

  3. Using generics

    module Bottom =
        // will never return, but implies null or zero for any type
        let create() = 
            exn "Unreachable code" |> raise 
            Unchecked.defaultof<_>

    This method uses the same method as raise and comes close to satisfying property (2) through isomorphism. However, as noted before, the CLR does not allow a type like Bottom, so this is as close as one can get (I think).

  4. Implicitly, the type System.Void has these properties, but F# prevents you from using it, as does C#. It's only there as a courtesy to the type system.

TysonMN commented 3 years ago

Thanks for your comment here and your comments in that language suggestion. I agree with your conclusions. Thanks for the summary :)