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

前端 未结 4 1862
春和景丽
春和景丽 2021-01-01 02:45

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: Prope         


        
相关标签:
4条回答
  • 2021-01-01 03:15

    When I put your code in F# Interactive, it seems to make 'record' a generic param. Maybe it works differently in the normal compiler. Anyway, it is probably picking up that obj type due to the first argument of GetValue being type obj.

    I'm sorry I can't test this right now, but give this a shot. The box function uses a generic param, so that might do the trick.

    let rec getSafeValue record (prop: PropertyInfo) = 
        match prop.GetValue(box record, null) with
        | :? string as str -> "'" + str + "'"
        | :? Option<_> as opt -> 
            match opt with
            | Some v -> getSafeValue v prop
            | None -> "null"
        | _ as v -> v.ToString()
    
    0 讨论(0)
  • 2021-01-01 03:27

    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()
    
    0 讨论(0)
  • 2021-01-01 03:30

    Some v -> getSafeValue v prop will only work if v is of the same type as record. (or deriving from that that type) otherwise the first line will fail. you can't say prop.GetValue(record,null) unless the property pointed to by prop makes sense (aka is part of the type) in the context of the first argument.

    If it's the same type you can do:

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

    but if the type of v is derived from 'a it will match the last case so for the above to work they'd need to be exactly the same type

    0 讨论(0)
  • 2021-01-01 03:34

    This can't work in F# without covariance. Assuming that you're happy for v to be of type obj, you want to be able to treat Option<anything> as if it was Option<obj>. Without covariance, Option<anything> and Option<obj> are independent types.

    0 讨论(0)
提交回复
热议问题