2010-05-15 20 views
20

Estoy tratando de definir una función, factorizar, que utiliza restricciones de tipo estructural (requiere miembros estáticos cero, uno, + y /) similar a Seq.sum para que se pueda usar con int , long, bigint, etc. Parece que no puedo obtener la sintaxis correcta y no puedo encontrar muchos recursos sobre el tema. Esto es lo que tengo, por favor ayuda.F # Restricciones del tipo de miembro estático

let inline factorize (n:^NUM) = 
    ^NUM : (static member get_Zero: unit->(^NUM)) 
    ^NUM : (static member get_One: unit->(^NUM)) 
    let rec factorize (n:^NUM) (j:^NUM) (flist: ^NUM list) = 
     if n = ^NUM.One then flist 
     elif n % j = ^NUM.Zero then factorize (n/j) (^NUM.One + ^NUM.One) (j::flist) 
     else factorize n (j + ^NUM.One) (flist) 
    factorize n (^NUM.One + ^NUM.One) [] 

Respuesta

21

Así es como me gustaría escribir que:

module NumericLiteralG = begin 
    let inline FromZero() = LanguagePrimitives.GenericZero 
    let inline FromOne() = LanguagePrimitives.GenericOne 
end 

let inline factorize n = 
    let rec factorize n j flist = 
    if n = 1G then flist 
    elif n % j = 0G then factorize (n/j) j (j::flist) 
    else factorize n (j + 1G) (flist) 
    factorize n (1G + 1G) [] 

El tipo inferido para factorize aquí es demasiado general, pero la función funcionará como era de esperar. Puede forzar una firma más sano y conjunto de restricciones si desea mediante la adición de tipos explícitas a algunas de las expresiones genéricas:

let inline factorize (n:^a) : ^a list = 
    let (one : ^a) = 1G 
    let (zero : ^a) = 0G 
    let rec factorize n (j:^a) flist = 
    if n = one then flist 
    elif n % j = zero then factorize (n/j) j (j::flist) 
    else factorize n (j + one) (flist) 
    factorize n (one + one) [] 
+0

Guau, eso es increíble. –

+1

Tienes razón, esto funciona, es una gran sorpresa para mí :-). No estoy seguro de lo que está sucediendo aquí, porque 'factorize' se compila como una función genérica. Utiliza la aplicación dinámica de 'GetZero' (que es probablemente similar al uso de' NumericAssociations'), pero no estoy seguro de cómo funciona esto (sin registro explícito de las operaciones para su propio tipo). Si entiendes cómo funciona esto, estaría bastante interesado en los detalles :-). –

+0

Acabo de notar la buena optimización que ha agregado en el caso elif al algoritmo mismo. –

4

En primer lugar, aquí es un ejemplo trivial que muestra cómo la sintaxis debe ser similar:

let inline zero< ^NUM when ^NUM : (static member get_Zero: unit-> ^NUM)> 
    (n:^NUM) = 
    (^NUM : (static member get_Zero : unit -> ^NUM)()) 

En algunos casos, no es necesario escribir las limitaciones expresamente (el F # compilador realidad advertirle sobre eso si escribe lo anterior), porque algunos miembros estáticos son bien conocidos por el compilador y existen funciones estándar para usarlos. Por lo tanto, puede utilizar la función y el compilador inferirá la restricción:

let inline zero (n:^T) = 
    LanguagePrimitives.GenericZero<^T> 

Desafortunadamente, esto realmente no te ayuda, porque las funciones recursivas no pueden ser declarados como inline (por razones obvias - el compilador puede no coloca en línea de la funciona en tiempo de compilación, porque no sabe cuántas veces), por lo que las restricciones estáticas probablemente no sean lo suficientemente potentes para su problema.

[EDITAR: Esto es realmente posible que algunas funciones (véase la respuesta de kvb)]

Creo que necesita NumericAssociations lugar, los cuales fueron alreaday discuten in this question (éstos se procesan en tiempo de ejecución, por lo que son más lento, pero se utilizan para implementar, por ejemplo, el tipo de matriz F #, la matriz puede almacenar en caché la información obtenida dinámicamente, por lo que es razonablemente eficiente).

7

Inspirado en respuesta usando NumericLiterals del KVB, me llevaron a desarrollar un enfoque que nos permita para forzar firmas de tipo "cuerdas" sin tener que agregar anotaciones de tipo extensas.

En primer lugar, definir algunas funciones de ayuda y tipo de contenedor para primitivas del lenguaje:

let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a> 
let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a> 
let inline two_of (target:'a) : 'a = one_of(target) + one_of(target) 
let inline three_of (target:'a) : 'a = two_of(target) + one_of(target) 
let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target) 

let inline any_of (target:'a) (x:int) : 'a = 
    let one:'a = one_of target 
    let zero:'a = zero_of target 
    let xu = if x > 0 then 1 else -1 
    let gu:'a = if x > 0 then one else zero-one 

    let rec get i g = 
     if i = x then g 
     else get (i+xu) (g+gu) 
    get 0 zero 

type G<'a> = { 
    negone:'a 
    zero:'a 
    one:'a 
    two:'a 
    three:'a 
    any: int -> 'a 
}  

let inline G_of (target:'a) : (G<'a>) = { 
    zero = zero_of target 
    one = one_of target 
    two = two_of target 
    three = three_of target 
    negone = negone_of target 
    any = any_of target 
} 

entonces tenemos:

let inline factorizeG n = 
    let g = G_of n 
    let rec factorize n j flist = 
     if n = g.one then flist 
     elif n % j = g.zero then factorize (n/j) j (j::flist) 
     else factorize n (j + g.one) (flist) 
    factorize n g.two [] 

[Editar: debido a un fallo aparente con F # 2.0 /. NET 2.0, factorizen, factorizeL y factorizeI a continuación se ejecutan significativamente más lento que factorizeG cuando se compila en modo Release, pero si no se ejecuta un poco más rápido como se esperaba - ver F# performance question: what is the compiler doing?]

O podemos tomar un par de paso más (inspirado por el experto F #, p.110):

let inline factorize (g:G<'a>) n = //' 
    let rec factorize n j flist = 
     if n = g.one then flist 
     elif n % j = g.zero then factorize (n/j) j (j::flist) 
     else factorize n (j + g.one) (flist) 
    factorize n g.two [] 

//identical to our earlier factorizeG 
let inline factorizeG n = factorize (G_of n) n 

let gn = G_of 1 //int32 
let gL = G_of 1L //int64 
let gI = G_of 1I //bigint 

//allow us to limit to only integral numeric types 
//and to reap performance gain by using pre-computed instances of G 
let factorizen = factorize gn 
let factorizeL = factorize gL 
let factorizeI = factorize gI 

Además, aquí es una versión extendida de NumericLiteralG de kvb la que nos permite utilizar "2G", " -8G ", etc.Aunque no pude encontrar la manera de implementar una estrategia de memorización (aunque eso debería ser factible para G.any).

module NumericLiteralG = 
    let inline FromZero() = LanguagePrimitives.GenericZero 
    let inline FromOne() = LanguagePrimitives.GenericOne 
    let inline FromInt32(n:int):'a = 
     let one:'a = FromOne() 
     let zero:'a = FromZero() 
     let nu = if n > 0 then 1 else -1 
     let gu:'a = if n > 0 then one else zero-one 

     let rec get i g = 
      if i = n then g 
      else get (i+nu) (g+gu) 
     get 0 zero 
Cuestiones relacionadas