在没有指定实例类型的情况下,F#中是否存在对泛型类型进行类型测试的方法?

时间:2010-06-30 16:01:40

标签: f#

我正在尝试模式匹配我关心SQL生成的几种类型。理想情况下我想这样做:

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()

问题在于,Option<_>的类型参数会获得与record匹配的约束,最终只有obj

我知道我可以做一些基于反射的检查(检查它是一个通用类型,并且它是基于名称的选项类型),但我宁愿避免这种情况,如果可能的话

4 个答案:

答案 0 :(得分:9)

不,使用F#的内置结构没有好办法。但是,您可以为此类事物构建自己的可重用活动模式:

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"

现在你的功能看起来像这样:

let rec getSafeValue (item:obj) = 
    match item with
    | :? string as str -> "'" + str + "'"
    | UC <@ Some @> [v] -> getSafeValue v
    | UC <@ None @> [] -> "null"
    | _ as v -> v.ToString()

答案 1 :(得分:0)

如果没有协方差,这在F#中无效。假设您很高兴v属于obj类型,您希望能够将Option<anything>视为Option<obj>。如果没有协方差,Option<anything>Option<obj>是独立的类型。

答案 2 :(得分:0)

当我把你的代码放在F#Interactive中时,它似乎使'record'成为一个通用的参数。也许它在普通编译器中的工作方式不同。无论如何,由于obj的第一个参数是类型GetValue,它可能会提取obj类型。

对不起,我现在无法测试,但请试一试。 box函数使用通用参数,因此可以做到这一点。

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()

答案 3 :(得分:0)

有些v - &gt; getSafeValue v prop仅在v与记录类型相同时才有效。 (或从该类型派生)否则第一行将失败。你不能说prop.GetValue(record,null),除非prop指向的属性在第一个参数的上下文中有意义(也就是类型的一部分)。

如果它是同一类型,你可以这样做:

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()

但如果v的类型来自'a它将匹配最后一个案例,那么为了使上述工作,他们需要完全相同的类型