David Lapeš
David Lapeš

Reputation: 352

f# pattern matching sequence :? seq<_> (IEnumerable)

I want to send a result to a single method that outputs the content to the Console/Log

I had hoped to detect whether the result contained an IEnumerable and traverse that collection for its results.

This fails to recognize a seq and simply identifies it as Other Object.

Sorry for the verbosity.

let rec LogResultGeneric (logInfo: string -> unit, logError: string -> unit) (result: Result<_, _>) =
    let innerSelect (item: _) =
        match item |> box with
        | :? Result<_, _> as res ->
            "RESULT" |> logInfo
            res |> LogResultGeneric(logInfo, logError)
        | _ ->
            "VALUE" |> logInfo
            item |> LogValueGeneric logInfo

    "DISPLAY OUTCOME : " + result.ToString() |> logInfo 
    match result with
    | Error msg ->
        "ERROR RESULT" |> logError
        match msg |> box with
        | :? string as msg -> msg |> logError
        | _ -> msg.ToString() |> logError

    | Ok payload ->
        "OK RESULT" |> logInfo

        match payload |> box with
        | :? seq<obj> as s ->
            "IENUMERABLE" |> logInfo
            s
            |> Seq.iter innerSelect
        | _ ->
            "VALUE" |> logInfo
            payload |> LogValueGeneric logInfo
        |> ignore

Upvotes: 1

Views: 549

Answers (2)

kaefer
kaefer

Reputation: 5741

As an extended comment to Tomas's answer: As long as you are willing to use Reflection, you can invoke generic methods directly, without reliance on generic types. In this way we may deal with the Result<_,_> type discriminated union too.

type TypePrinter =
    static member PrintOption o =
        match o with
        | None -> printfn "Nothing"
        | Some x -> printfn "Something: %A" x
    static member PrintResult r =
        match r with
        | Error e-> printfn "Error: %A" e
        | Ok x -> printfn "Ok: %A" x

let invokeGenericMethod methodName o =
    typeof<TypePrinter>.GetMethod(methodName)
        .MakeGenericMethod(o.GetType().GetGenericArguments())
        .Invoke(null, [| o |]) |> ignore
let isOption o =
    o.GetType().IsGenericType &&
    o.GetType().GetGenericTypeDefinition() =
        typedefof<Option<_>>
let isResult o =
    o.GetType().IsGenericType &&
    o.GetType().BaseType.GetGenericTypeDefinition() =
        typedefof<Result<_,_>>

let print payload = 
    match box payload with 
    | :? System.Collections.IEnumerable as ie ->
        for x in ie do
            printfn "ITEM: %A" x
    | null -> 
        printfn "NULL (or None)"
    | v when isOption v ->
        invokeGenericMethod "PrintOption" v
    | v when isResult v ->
        invokeGenericMethod "PrintResult" v
    | v ->
        printfn "VALUE: %A" v

print [1..3]
print "ABC"
print (Some 10)
print None
print (Ok 42)
print (Error "Oh no")

Upvotes: 2

Tomas Petricek
Tomas Petricek

Reputation: 243041

Pattern matching a value against a generic type is tricky - because the compiler does not statically know what the 'a in seq<'a> should be. Pattern matching against seq<obj> also does not work, because e.g. seq<int> does not implement seq<obj>.

However, for collections, you are lucky because a generic IEnumerable<'T> inherits from non-generic IEnumerable and so you can use that:

let print payload = 
  match box payload with 
  | :? System.Collections.IEnumerable as ie ->
      let en = ie.GetEnumerator()
      while en.MoveNext() do
        printfn "ITEM: %A" en.Current
  | v -> 
      printfn "VALUE: %A" v

print [1;2;3]
print "ABC"

Getting something like this to work for generic types that do not have a non-generic base type such as option<'T> is harder and generally requires reflection.

One trick is to have a generic helper class and use reflection to specialize that to the right type and then invoke the method (but generally, this is pretty ugly):

type OptionPrinter<'T> = 
  static member Print(o:option<'T>) = 
    match o with 
    | None -> printfn "Nothing"
    | Some v -> printfn "Something: %A" v 

let print payload = 
  match box payload with 
  | :? System.Collections.IEnumerable as ie ->
      let en = ie.GetEnumerator()
      while en.MoveNext() do
        printfn "ITEM: %A" en.Current
  | null -> 
      printfn "NULL (or None)"
  | v when v.GetType().IsGenericType && 
          v.GetType().GetGenericTypeDefinition() = typedefof<option<_>> ->
      let tya = v.GetType().GetGenericArguments()
      let ty = typedefof<OptionPrinter<_>>.MakeGenericType(tya)
      ty.GetMethod("Print").Invoke(null, [| v |]) |> ignore
  | v -> 
      printfn "VALUE: %A" v

print [1;2;3]
print "ABC"
print (Some 10)
print None

Upvotes: 5

Related Questions