2011-11-24 16 views
16

Por qué es que puedo hacer:¿Por qué no puedo agregar Integer to Double en Haskell?

1 + 2.0 

pero cuando intento:

let a = 1 
let b = 2.0 
a + b 

<interactive>:1:5: 
    Couldn't match expected type `Integer' with actual type `Double' 
    In the second argument of `(+)', namely `b' 
    In the expression: a + b 
    In an equation for `it': it = a + b 

Esto parece simplemente extraño! ¿Alguna vez te tropieza?

P.S .: Sé que "1" y "2.0" son constantes polimórficas. Eso no es lo que me preocupa. Lo que me preocupa es por qué haskell hace una cosa en el primer caso, pero otra en el segundo!

+3

Como dice el meme, "necesita más' fromIntegral' ". –

+1

Me recuerda a 'x.f()' frente a 'g = x.f; g(); 'en Javascript. Apesta cuando las variables rompen la abstracción del modelo de sustitución. – hugomg

Respuesta

7

Puede usar GHCI para aprender un poco más sobre esto. Use el comando :t para obtener el tipo de una expresión.

Prelude> :t 1 
1 :: Num a => a 

Así 1 es una constante que puede ser cualquier tipo numérico (Double, Integer, etc.)

Prelude> let a = 1 
Prelude> :t a 
a :: Integer 

lo tanto, en este caso, Haskell inferir el tipo concreto para a es Integer. Del mismo modo, si escribe let b = 2.0, entonces Haskell infiere el tipo Double. El uso de let hizo que Haskell infiriera un tipo más específico que (quizás) era necesario, y eso lleva a su problema. (Alguien con más experiencia que yo quizás pueda comentar por qué este es el caso.) Dado que (+) tiene el tipo Num a => a -> a -> a, los dos argumentos deben tener el mismo tipo.

Puedes solucionar este problema con la función fromIntegral:

Prelude> :t fromIntegral 
fromIntegral :: (Num b, Integral a) => a -> b 

Esta función convierte los tipos enteros a otros tipos numéricos.Por ejemplo:

Prelude> let a = 1 
Prelude> let b = 2.0 
Prelude> (fromIntegral a) + b 
3.0 
+3

La [restricción de monomorfismo] (http://www.haskell.org/haskellwiki/Monomorphism_restriction) es la razón por la cual 'let' hace que se elija un tipo específico. Puede anular esto dándole una firma de tipo, o haciendo ': set -XNoMonomorphismRestriction' en GHCi (o el indicador equivalente al compilar). – hammar

+0

¿Hay necesidad de establecer la bandera cuando se está cumpliendo? Pensé que el compilador no hace "default" ... – drozzy

+0

@drozzy: Sí. GHCi solo tiene reglas de incumplimiento ligeramente diferentes. En particular, GHCi permite un conjunto mayor de restricciones de clase predeterminadas, y agrega '()' a la lista de tipos predeterminados. – hammar

19

La firma de tipo (+) se define como Num a => a -> a -> a, lo que significa que funciona en cualquier miembro de la clase de tipo Num, pero ambos argumentos deben ser del mismo tipo.

El problema aquí es con GHCI y el orden que establece los tipos, no Haskell sí mismo. Si pusiera cualquiera de sus ejemplos en un archivo (usando do para las expresiones let) se compilaría y correría bien, porque GHC usaría toda la función como contexto para determinar los tipos de los literales 1 y 2.0.

Todo lo que está sucediendo en el primer caso es que GHCI adivina los tipos de los números que está ingresando. El más preciso es un Double, por lo que se supone que el otro se supone que es un Double y ejecuta el cálculo. Sin embargo, cuando utiliza la expresión let, solo tiene un número para trabajar, por lo que decide 1 es Integer y 2.0 es Double.

EDITAR: GHCI no está realmente "adivinando", está usando reglas de incumplimiento de tipo muy específicas que se definen en el Informe Haskell. Puede leer un poco más sobre eso here.

+3

Primero, subí. Pero como tengo el hábito de pensar mal: un sistema de tipo estático puede soportar agregar números de diferentes tipos sin problemas. Es solo que la definición de Haskell se conformó con ('(+) :: Num a => a -> a -> a') no lo permite. (Sin embargo, no juega muy bien con la inferencia de tipo, que es probablemente la razón por la que no se hizo). Además, su respuesta puede ser más clara si explicitamente ("adivinar" no le hace justicia en mi humilde opinión) explica el tipo de incumplimiento y el hecho de que los literales son polimórficos. – delnan

+3

"Haskell tiene un sistema de tipo estático, por lo que nunca podrá agregar dos tipos diferentes". Eso es un poco de simplificación excesiva.Muchos idiomas tienen sistemas de tipo estático y permiten agregar ints a dobles. La palabra clave aquí es que Haskell no tiene conversiones implícitas. – sepp2k

+0

Sí, no me sentí como si tuviera un control suficiente sobre el tipo de incumplimiento y tal para explicarlo. Siéntase libre de agregar su propia respuesta con esas cosas. –

11

Los primeros trabajos porque numéricos literales son polimórficos (. Se interpretan como fromInteger literal resp fromRational literal), por lo que en 1 + 2.0, que realmente tienen fromInteger 1 + fromRational 2, en ausencia de otras limitaciones, el tipo de resultado se predetermina a Double.

El segundo no funciona debido a la restricción de monomorfismo. Si vincula algo sin una firma de tipo y con un enlace de patrón simple (name = expresion), a esa entidad se le asigna un tipo monomórfico. Para el literal 1, tenemos una restricción Num, por lo tanto, de acuerdo con defaulting rules, su tipo está predeterminado en Integer en el enlace let a = 1. Del mismo modo, el tipo de literal fraccionario está predeterminado en Double.

Funcionará, por cierto, si :set -XNoMonomorphismRestriction en ghci.

El motivo de la restricción de monomorfismo es evitar la pérdida de compartición, si ve un valor que se parece a una constante, no espera que se calcule más de una vez, pero si tiene un tipo polimórfico, se volvería a calcular cada vez que se use.

3

Otros han abordado muchos aspectos de esta pregunta bastante bien. Me gustaría decir unas palabras sobre la razón detrás de por qué + tiene el tipo de firma Num a => a -> a -> a.

En primer lugar, la clase de tipo Num no tiene forma de convertir una instancia artbitraria de Num en otra. Supongamos que tengo un tipo de datos para números imaginarios; todavía son números, pero realmente no puedes convertirlos en un Int.

En segundo lugar, ¿qué tipo de firma prefieres?

(+) :: (Num a, Num b) => a -> b -> a 
(+) :: (Num a, Num b) => a -> b -> b 
(+) :: (Num a, Num b, Num c) => a -> b -> c 

Después de considerar las otras opciones, se dan cuenta de que a -> a -> a es la opción más sencilla. Los resultados polimórficos (como en la tercera sugerencia anterior) son geniales, pero a veces pueden ser demasiado genéricos para ser usados ​​convenientemente.

En tercer lugar, Haskell no es Blub. La mayoría, aunque podría decirse que no todas, las decisiones de diseño sobre Haskell no toman en cuenta las convenciones y expectativas de los lenguajes populares. Con frecuencia me gusta decir que el primer paso para aprender Haskell es desaprender primero todo lo que crees que sabes sobre programación. Estoy seguro de que la mayoría, si no todos, los Haskeller experimentados se han visto frustrados por la clase de Num y otras curiosidades de Haskell, porque la mayoría aprendió un lenguaje más "dominante" primero. Pero sé paciente, eventualmente alcanzarás Haskell nirvana. :)

Cuestiones relacionadas