simbolos - Función de sobrecarga de firmas haskell
programa en haskell (5)
Recibo el siguiente mensaje de error cuando compilo:
Firma de tipo duplicado:
weightedMedian.hs: 71: 0-39: findVal :: [ValPair] -> Double -> Double
weightedMedian.hs: 68: 0-36: findVal :: [ValPair] -> Int -> Double
Mi solución es tener findValI y findValD. Sin embargo, findValI simplemente convierte el tipo Int en un Double y llama a findValD.
Además, no puedo hacer coincidir el patrón en los tipos de Num (Int, Double), así que no puedo simplemente cambiar la firma de tipo a
findVal :: [ValPair] -> Num -> Double
En muchos idiomas no necesitaría nombres diferentes. ¿Por qué necesito nombres diferentes en Haskell? ¿Sería esto difícil de agregar al lenguaje? ¿O hay dragones allí?
El polimorfismo ad-hoc (y la sobrecarga de nombres) se proporciona en Haskell mediante clases de tipos:
class CanFindVal a where
findVal :: [ValPair] -> a -> Double
instance CanFindVal Double where
findVal xs d = ...
instance CanFindVal Int where
findVal xs d = findVal xs (fromIntegral d :: Double)
Tenga en cuenta que en este caso, dado que findVal
"realmente" necesita un Double
, siempre lo tomaría un doble, y cuando necesito pasarlo un int, solo use fromIntegral
en el sitio de la llamada. Por lo general, se desean clases de tipos cuando en realidad hay un comportamiento o lógica diferente, en lugar de promiscuamente.
En este caso, creo que es fácil escribir una función que maneje Int y Double para el segundo argumento. Simplemente escriba findVal
para que se llame realToFrac
en el segundo argumento. Eso convertirá el Int
en un Double
y solo dejará un Double
solo. Luego, deja que el compilador deduzca el tipo por ti, si eres perezoso.
En muchos otros lenguajes de programación, puede declarar (más o menos) las funciones que tienen el mismo nombre pero otras cosas diferentes en sus firmas, como los diferentes tipos de parámetros. Esto se denomina sobrecarga y ciertamente es la forma más popular de lograr un polimorfismo ad hoc.
Haskell deliberadamente NO admite la sobrecarga porque sus diseñadores no lo consideran como la mejor manera de lograr el polimorfismo ad hoc. La forma de Haskell es más bien un polimorfismo restringido e implica declarar clases de tipo e instancias de clase
Haskell no admite la sobrecarga de estilo C ++ (bueno, lo mismo ocurre con las clases de tipos, pero no las usamos de la misma manera). Y sí, hay algunos dragones asociados a agregarlo, principalmente relacionados con la inferencia de tipos (se convierte en tiempo exponencial o indecidible o algo así). Sin embargo, ver un código de "conveniencia" como este no es muy común en Haskell. ¿Cuál es, un Int
o un Double
? Dado que su método Int
se delega al método Double
, mi conjetura es que Double
es el "correcto". Solo usa ese. Debido a la sobrecarga literal, todavía puede llamarlo como:
findVal whatever 42
Y los 42
serán tratados como un Double
. El único caso en que esto se rompe es si tienes algo que es fundamentalmente un Int
algún lugar y necesitas pasarlo como este argumento. Entonces use fromIntegral
. Pero si se esfuerza para que su código use el tipo "correcto" en todas partes, este caso será poco común (y cuando tenga que convertir, valdrá la pena llamar la atención).
Soportando tanto findVal :: [ValPair] -> Double -> Double
como findVal :: [ValPair] -> Int -> Double
requiere un polimorfismo ad-hoc (consulte http://www.haskell.org/haskellwiki/Ad-hoc_polymorphism ) que es generalmente peligroso. La razón es que el polimorfismo ad hoc permite cambiar la semántica con la misma sintaxis.
Haskell prefiere lo que se llama polimorfismo paramétrico. Ves esto todo el tiempo con firmas de tipo, donde tienes una variable de tipo.
Haskell admite una versión más segura de polimorfismo ad-hoc a través de clases de tipos.
Tienes tres opciones.
- Continúa lo que estás haciendo con un nombre de función explícito. Esto es razonable, incluso es usado por algunas bibliotecas c, por ejemplo, opengl.
- Utilice una clase de tipo personalizado. Esta es probablemente la mejor manera, pero es pesada y requiere una buena cantidad de código (según los estándares muy compactos de haskells). Mira la respuesta de sclv para el código.
- Intente usar una clase de tipo existente y (si usa GHC) obtenga el rendimiento con especializaciones.
Me gusta esto:
findVal :: Num a => [ValPair] -> a -> Double
{-# SPECIALISE findVal :: [ValPair] -> Int -> Double #-}
{-# SPECIALISE findVal :: [ValPair] -> Double -> Double #-}
findVal = ...