2010-06-30 8 views
5

Estoy tratando de emparejar patrones con algunos tipos que me importan para la generación de SQL. Idealmente me gustaría hacer esto:¿Hay alguna forma en F # de testear frente a un tipo genérico sin especificar el tipo de instancia?

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

El problema es que aquí, el parámetro de tipo de Option<_> pone restricción para que coincida con la de record, que termina siendo simplemente obj.

Sé que puedo hacer un chequeo basado en la reflexión del dolor detrás de mí (compruebe que es un tipo genérico y que es un tipo de opción basado en el nombre), pero prefiero evitarlo si lo hago. posible.

Respuesta

9

No, no hay una buena manera de hacerlo utilizando las construcciones integradas de F #. Sin embargo, se podría construir su propio patrón activo reutilizable para este tipo de cosas:

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" 

Ahora su función podría ser algo como esto:

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

que es una manera ordenada de hacerlo. Terminé haciendo algo similar. – kolosy

0

Esto no puede funcionar en F # sin covarianza. Suponiendo que está contento de que v sea del tipo obj, desea que se pueda tratar Option<anything> como si fuera Option<obj>. Sin covarianza, Option<anything> y Option<obj> son tipos independientes.

+0

Para que quede claro, .NET (y por lo tanto F #) en realidad no tiene covarianza. –

+0

@GaneshSittampalam - .NET (a partir de 4.0) * does * have covariance: http://msdn.microsoft.com/en-us/library/dd799517.aspx. Creo que la razón por la que no se implementó en F # 2.0 es porque F # se había centrado principalmente en .NET 2.0 en el momento del lanzamiento. –

0

Cuando coloco su código en F # Interactive, parece que 'grabar' un param genérico. Tal vez funciona de manera diferente en el compilador normal. De todos modos, probablemente está tomando el tipo obj debido al primer argumento de GetValue que es del tipo obj.

Lo siento, no puedo probar esto ahora, pero le doy una oportunidad. La función box usa un param genérico, por lo que podría hacer el truco.

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

mismo trato: limita el registro y opta por escribir 'a – kolosy

0

Algunos v -> getSafeValue v prop solo funcionarán si v es del mismo tipo que el registro. (o derivado de ese tipo) de lo contrario la primera línea fallará. no se puede decir prop.GetValue (record, null) a menos que la propiedad apuntada por prop tenga sentido (aka es parte del tipo) en el contexto del primer argumento.

Si es del mismo tipo que puede hacer:

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

pero si el tipo de v se deriva de 'una que coincidirá con el último caso, por lo que para los anteriores a trabajar que necesitarían ser exactamente del mismo tipo

Cuestiones relacionadas