2009-02-01 32 views
43

Estoy tratando de averiguar cómo definir una función que funcione en múltiples tipos de parámetros (por ejemplo, int e int64). Según tengo entendido, la sobrecarga de funciones no es posible en F # (sin duda el compilador se queja). Tomemos por ejemplo la siguiente función.Funciones con los tipos de parámetros genéricos

let sqrt_int = function 
    | n:int -> int (sqrt (float n)) 
    | n:int64 -> int64 (sqrt (float n)) 

El compilador, por supuesto, se queja de que la sintaxis no es válida (restricciones de tipo de coincidencia de patrones no son compatibles con lo que parece), aunque creo que esto ilustra lo que me gustaría lograr: una función que opera en varios tipos de parámetros y devuelve un valor del tipo correspondiente. Tengo la sensación de que esto es posible en F # usando una combinación de tipos genéricos/inferencia de tipo/coincidencia de patrones, pero la sintaxis me ha eludido. También he intentado usar el:? operador (pruebas de tipo dinámico) y cuando hace una cláusula en el bloque de coincidencia de patrones, pero esto todavía produce errores de todo tipo.

Como soy bastante nuevo en el lenguaje, es posible que esté tratando de hacer algo imposible aquí, por lo que le ruego me indique si hay una solución alternativa.

Respuesta

58

La sobrecarga suele ser un problema entre los lenguajes de escritura de tipo (al menos cuando, como F #, el sistema de tipos no es lo suficientemente potente como para contener clases de tipos). Hay una serie de opciones que tiene en F #:

  • Uso sobrecarga en los métodos (miembros de un tipo), en cuyo caso la sobrecarga funciona de manera similar como en otros lenguajes .Net (que pueda miembros de sobrecarga ad-hoc, las llamadas provistas se pueden distinguir por el número/tipo de parámetros)
  • Utilice restricciones de miembros "en línea", "^" y estáticos para la sobrecarga ad-hoc de funciones (esto es lo que la mayoría de los diversos operadores matemáticos necesitan para funcionar en int/float/etc; la sintaxis aquí es rara, esto se usa poco aparte de la biblioteca F #)
  • Simula clases de tipo pasando un parámetro extra de diccionario de operaciones (esto es lo que INumeric hace en uno de el F # PowerPack li braries para generalizar varios algoritmos de matemáticas para los tipos definidos por el usuario arbitrarias)
  • caer de nuevo a tipado dinámico (pasa en un parámetro 'obj', hacer una prueba de tipo dinámico, una excepción de tiempo de ejecución para el mal tipo)

para su ejemplo particular, probablemente sólo tiene que utilizar la sobrecarga de métodos:

type MathOps = 
    static member sqrt_int(x:int) = x |> float |> sqrt |> int 
    static member sqrt_int(x:int64) = x |> float |> sqrt |> int64 

let x = MathOps.sqrt_int 9 
let y = MathOps.sqrt_int 100L 
+0

Buena aclaración - Creo que entiendo lo que está sucediendo ahora. Saludos por eso. Habiendo leído sobre las cosas, Parece que F # aún falta algunas de las características agradables idiomas funcionales como Haskell. Quizás estas características se implementarán pronto ahora que F # es un lenguaje de .NET de primera clase. – Noldorin

+0

no estoy seguro de si quieres que esta sea una pregunta 'viviente' o no, pero dado que la versión actual de f # ya no necesita el OverloadId attrib (yey!) Es posible que desee ajustar la respuesta ... – ShuggyCoUk

+0

Gracias, ya había actualizado el código, pero se olvidó de actualizar la prosa. – Brian

14

Sí, esto se puede hacer. Eche un vistazo al this hubFS thread.

En este caso, la solución sería:

let inline retype (x:'a) : 'b = (# "" x : 'b #) 
let inline sqrt_int (n:'a) = retype (sqrt (float n)) : 'a 

Advertencia: no en tiempo de compilación comprobación de tipos. Es decir. sqrt_int "blabla" compila bien, pero obtendrá FormatException en tiempo de ejecución.

+0

Gracias, esa parece ser la solución (aunque no es tan sencillo como podría haber esperado). Solo para aclarar, ¿me gustaría algo como esto? let inline sqrt_int (n:^a) = retype (sqrt (float n)):^a – Noldorin

+1

Sí, eso funciona. Sin embargo, tenga en cuenta que con esto pierde la comprobación de tipos en tiempo de compilación. Es decir. sqrt_int "blabla" comprueba los tipos, aunque obtendrás una FormatException en tiempo de ejecución. –

+0

Ok, entonces realmente no tiene sentido usar el operador hat en este caso, ¿verdad? Si resulta que estoy usando un operador aritmético como * en la función (antes del elenco), ¿eso aseguraría la verificación en tiempo de compilación? – Noldorin

2

no tomar distancia de las respuestas correctas que se facilitan, sino que puede, de hecho restricciones de tipo uso en la coincidencia de patrones. La sintaxis es la siguiente:

| :? type -> 

O si desea combinar la comprobación de tipos y de calidad:

| :? type as foo -> 
+0

Eso es lo que inicialmente pensé que podría hacer. Desafortunadamente da un error de "coerción de tiempo de ejecución" (error FS0008). Junto con la función de volver a escribir proporcionada en un enlace en la publicación de mausch, sin embargo, debería funcionar como una alternativa a la palabra clave en línea si la entiendo correctamente. – Noldorin

+0

la coacción de tiempo de ejecución se puede evitar encajonando la variable con la que coincide el tipo usando 'match box variable with' – Remko

9

Ésta es otra manera el uso de cheques de tipo en tiempo de ejecución ...

let sqrt_int<'a> (x:'a) : 'a = // ' 
    match box x with 
    | :? int as i -> downcast (i |> float |> sqrt |> int |> box) 
    | :? int64 as i -> downcast (i |> float |> sqrt |> int64 |> box) 
    | _ -> failwith "boo" 

let a = sqrt_int 9 
let b = sqrt_int 100L 
let c = sqrt_int "foo" // boom 
+0

Interesante. ¡Ahora tengo demasiadas opciones! Una pregunta: por qué necesita el especificador genérico <'a> cuando especifica una restricción de tipo en x. Pensé que eran sintaxis equivalentes. – Noldorin

+0

Puede omitir el <'a> (pruébelo). Tenga en cuenta la diferencia de rendimiento potencial; la sobrecarga de método determina qué versión en tiempo de compilación, mientras que esta versión realiza una comprobación de tipo en tiempo de ejecución. (Podría ser que 'sqrt' abrume estas consideraciones, sin embargo, no he medido.) – Brian

+0

Y, por supuesto, la otra diferencia es que esta versión se compila (y arroja en tiempo de ejecución) para los no-ints, mientras que la versión de sobrecarga del método error en tiempo de compilación para no-ints. – Brian

13

Esto funciona:

type T = T with 
    static member ($) (T, n:int ) = int (sqrt (float n)) 
    static member ($) (T, n:int64) = int64 (sqrt (float n)) 

let inline sqrt_int (x:'t) :'t = T $ x 

Utiliza las restricciones estáticas y sobrecarga, lo que hace una búsqueda en tiempo de compilación del tipo de argumento.

Las restricciones estáticas se generan automáticamente en presencia de un operador (operador $ en este caso), pero siempre se puede escribir a mano:

type T = T with 
    static member Sqr (T, n:int ) = int (sqrt (float n)) 
    static member Sqr (T, n:int64) = int64 (sqrt (float n)) 

let inline sqrt_int (x:'N) :'N = ((^T or ^N) : (static member Sqr: ^T * ^N -> _) T, x) 

Más información este here.

+1

Esta es una muy buena mejora sobre la solución proporcionada por Brian y Mauricio, tiene el beneficio de una función sin la notación de punto (comparar Brian) y agrega comprobación de tipo en tiempo de compilación (comparar con Mauricio). ¿Le gustaría elaborar en su respuesta sobre cómo funciona esto y si la definición del operador es un requisito? – Abel

+2

Gracias @Abel, elaboré un poco más sobre la solución e incluí un enlace a una entrada de blog con más detalles. La respuesta de Mauricio tiene un enfoque muy diferente, que también es válido, siempre usa el mismo código para todos los tipos al convertir a '' float'', que es menos código, pero si quieres trabajar con enteros grandes, puedes toparte con una limitación . – Gustavo

Cuestiones relacionadas