Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern matching against generic type using 'flexible' type parameter

match value with
| :? list<#SomeType> as l -> l //Is it possible to match any list of a type derived from SomeType?
| _ -> failwith "doesn't match"
like image 336
Daniel Avatar asked Sep 21 '10 22:09

Daniel


5 Answers

As already pointed out, there is no way to do this directly (pattern matching can only bind values, but it cannot bind new type variables). In addition to the (more general) workaround by kvb you can use the fact that all collections implement non-generic IEnumerable, so you can check for this type:

match box value with 
| :? System.Collections.IEnumerable as l when 
     // assumes that the actual type of 'l' is 'List<T>' or some other type
     // with single generic type parameter (this is not fully correct, because
     // it could be other type too, but we can ignore this for now)
     typedefof<SomeType>.IsAssignableFrom
       (value.GetType().GetGenericArguments().[0]) -> 
   l |> Seq.cast<SomeType>
| _ -> failwith "doesn't match"

The code tests whether the value is a non-generic IEnumerable and whether the type parameter is subtype of SomeType. In that case, we got a list of some derived type, so we can cast it to a sequence of SomeType values (this is slightly different than working with list of values of the derived types, but it shouldn't matter for practical purposes).

like image 178
Tomas Petricek Avatar answered Nov 01 '22 11:11

Tomas Petricek


No, it's unfortunately not possible to do something like this - the CLR doesn't provide any efficient way of doing that kind of type test. See How to cast an object to a list of generic type in F# and F# and pattern matching on generics in a non-generic method implementing an interface for a few (rather ugly) solutions.

like image 22
kvb Avatar answered Nov 01 '22 10:11

kvb


I later needed something similar for matching Lazy instances. Here's my solution, in case anyone finds it helpful.

let (|Lazy|_|) (value : obj) =
    if box value <> null then
        let typ = value.GetType()
        if typ.IsGenericType && typ.GetGenericTypeDefinition() = typedefof<Lazy<_>> then
            Some(typ.GetGenericArguments().[0])
        else None
    else None

Usage:

match value with
| Lazy typ when typeof<SomeType>.IsAssignableFrom(typ) -> (value :?> Lazy<_>).Value
| _ -> failwith "not an instance of Lazy<#SomeType>"
like image 45
Daniel Avatar answered Nov 01 '22 09:11

Daniel


According to the F# 2.0 specification, par. 14.5.2 (Solving Subtype Constraints), it will not work, because: "F# generic types do not support covariance or contravariance."

like image 1
Marc Sigrist Avatar answered Nov 01 '22 09:11

Marc Sigrist


Not the cleanest, but effective:

let matchType<'T> () =
    try
        let o = Activator.CreateInstance<'T> ()
        match box o with
        | :? Type1 -> printfn "Type1"
        | :? Type2 -> printfn "Type2"
        | _ -> failwith "unknown type"
    with
    | ex -> failwith "%s" (ex.ToString())
like image 1
Remko Avatar answered Nov 01 '22 11:11

Remko