Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way in F# to type-test against a generic type without specifying the instance type?

Tags:

f#

I'm trying to pattern match against a few types that I care about for SQL generation. Ideally I'd like to do this:

let rec getSafeValue record (prop: PropertyInfo) = 
    match prop.GetValue(record, null) with
    | :? string as str -> "'" + str + "'"
    | :? Option<_> as opt -> 
        match opt with
        | Some v -> getSafeValue v prop
        | None -> "null"
    | _ as v -> v.ToString()

The problem is that here, the type parameter to Option<_> gets constrained to match that of record, which ends up being just obj.

I know I can do some pain-in-the-behind reflection-based check (check that it's a generic type and that it's an option type based on the name), but I'd rather avoid that if at all possible.

like image 328
kolosy Avatar asked Jun 30 '10 16:06

kolosy


1 Answers

No, there's no good way to do this using F#'s built-in constructs. However, you could build your own reusable active pattern for this sort of thing:

open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.Patterns

let (|UC|_|) e o =
  match e with
  | Lambdas(_,NewUnionCase(uc,_)) | NewUnionCase(uc,[]) ->
      if (box o = null) then
        // Need special case logic in case null is a valid value (e.g. Option.None)
        let attrs = uc.DeclaringType.GetCustomAttributes(typeof<CompilationRepresentationAttribute>, false)
        if attrs.Length = 1
           && (attrs.[0] :?> CompilationRepresentationAttribute).Flags &&& CompilationRepresentationFlags.UseNullAsTrueValue <> enum 0
           && uc.GetFields().Length = 0
        then Some []
        else None
      else 
        let t = o.GetType()
        if FSharpType.IsUnion t then
          let uc2, fields = FSharpValue.GetUnionFields(o,t)
          let getGenType (t:System.Type) = if t.IsGenericType then t.GetGenericTypeDefinition() else t
          if uc2.Tag = uc.Tag && getGenType (uc2.DeclaringType) = getGenType (uc.DeclaringType) then
            Some(fields |> List.ofArray)
          else None
        else None
  | _ -> failwith "The UC pattern can only be used against simple union cases"

Now your function might look something like this:

let rec getSafeValue (item:obj) = 
    match item with
    | :? string as str -> "'" + str + "'"
    | UC <@ Some @> [v] -> getSafeValue v
    | UC <@ None @> [] -> "null"
    | _ as v -> v.ToString()
like image 119
kvb Avatar answered Oct 28 '22 19:10

kvb