Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is generic type in some expressions is matched as obj in F# pattern matching?

I have the following code:

type Message<'a> = | Message of 'a

let handleMessage message =
    match box message with
    | :? Message<_> -> printfn "Message"
    | _ -> printfn "Not message"

let handleMessageEx message =
    match box message with
    | :? Message<int> -> printfn "Message"
    | _ -> printfn "Not message"

handleMessage <| Message 1
handleMessage <| Message (1 :> obj)
handleMessageEx <| Message 1

The output in F# Interactive is the following:

Not message
Message 
Message

Why does the first statement result in "Not message"? I.e. when matching a boxed value F# is not able to detect that it is of a generic type Message<_> and unless I specify the underlying type it sets it to object (therefore failing match on (Message 1)).

like image 985
Vagif Abilov Avatar asked Oct 05 '16 13:10

Vagif Abilov


2 Answers

Whenever you see _ as a type parameter, think of it as a fresh type parameter that you don't care about. If we tweak your first definition accordingly:

let handleMessage message =
    match box message with
    | :? Message<'_a> -> printfn "Message"
    | _ -> printfn "Not message"

then the compiler gives us this helpful clue:

warning FS0064: This construct causes code to be less generic than indicated by the type annotations. The type variable '_a has been constrained to be type 'obj'.

The problem is that the type parameter has to be given some specific value, but the compiler has no basis on which to pick one so it defaults to obj, which is not what you want.

There's no great way to do this out of the box, but you can create an active pattern to simplify the experience somewhat: https://stackoverflow.com/a/2140485/82959.

like image 129
kvb Avatar answered Oct 11 '22 10:10

kvb


Simply speaking, Message<_> doesn't mean that you expect it to be generic, it means the compiler is free to infer the generic type argument to Message. Which, in absence of other constraints, results in making it obj. This also happens if you make the function generic - the generic type argument to the function (and, by extension, to Message type check) is decided at the call site:

let handleMessage<'a> (message: obj) =
    match message with
    | :? Message<'a> -> printfn "Message"
    | _ -> printfn "Not message"

handleMessage <| Message 1       // Not message -> 'a inferred to be obj
handleMessage<int> <| Message 1  // Message -> 'a inferred to be int

What you probably want to do is something like this:

let handleMessage message =
    let typ = message.GetType()
    match typ with
    | _ when typ.GetGenericTypeDefinition() = typedefof<Message<_>> -> printfn "Message"
    | _ -> printfn "Not message"

handleMessage <| Message 1
handleMessage <| Message (1 :> obj)

Here instead of doing a type test you inspect the generic type definitions of both.

like image 33
scrwtp Avatar answered Oct 11 '22 11:10

scrwtp