¿Alguien puede decirme por qué Haskell Prelude define dos funciones separadas para exponenciación (es decir, ^
y **
)? Pensé que se suponía que el sistema de tipos eliminaría este tipo de duplicación.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
¿Alguien puede decirme por qué Haskell Prelude define dos funciones separadas para exponenciación (es decir, ^
y **
)? Pensé que se suponía que el sistema de tipos eliminaría este tipo de duplicación.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Respuestas:
En realidad, hay tres operadores de exponenciación: (^)
, (^^)
y (**)
. ^
es exponenciación integral no negativa, ^^
es exponenciación entera y **
es exponenciación de punto flotante:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
La razón es la seguridad de tipos: los resultados de las operaciones numéricas generalmente tienen el mismo tipo que los argumentos de entrada. Pero no puedes elevar una Int
potencia de punto flotante y obtener un resultado de tipo Int
. Y así, el sistema de tipos le impide hacer esto: (1::Int) ** 0.5
produce un error de tipo. Lo mismo vale para (1::Int) ^^ (-1)
.
Otra forma de decir esto: los Num
tipos están cerrados bajo ^
(no es necesario que tengan un inverso multiplicativo), los Fractional
tipos están cerrados bajo ^^
, los Floating
tipos están cerrados bajo **
. Dado que no existe una Fractional
instancia para Int
, no puede elevarlo a una potencia negativa.
Idealmente, el segundo argumento de ^
estaría restringido estáticamente para no ser negativo (actualmente, 1 ^ (-2)
lanza una excepción en tiempo de ejecución). Pero no hay ningún tipo para números naturales en Prelude
.
El sistema de tipos de Haskell no es lo suficientemente poderoso como para expresar los tres operadores de potenciación como uno. Lo que realmente querrías es algo como esto:
class Exp a b where (^) :: a -> b -> a
instance (Num a, Integral b) => Exp a b where ... -- current ^
instance (Fractional a, Integral b) => Exp a b where ... -- current ^^
instance (Floating a, Floating b) => Exp a b where ... -- current **
Esto realmente no funciona incluso si activa la extensión de clase de tipo de parámetros múltiples, porque la selección de instancias debe ser más inteligente de lo que Haskell permite actualmente.
Int
y Integer
. Para poder tener esas tres declaraciones de instancia, la resolución de la instancia debe usar el retroceso, y ningún compilador de Haskell implementa eso.
No define dos operadores, ¡define tres! Del Informe:
Hay tres operaciones de exponenciación de dos argumentos: (
^
) eleva cualquier número a una potencia entera no negativa, (^^
) eleva un número fraccionario a cualquier potencia entera y (**
) toma dos argumentos de coma flotante. El valor dex^0
ox^^0
es 1 para cualquierax
, incluido cero;0**y
es indefinido.
Esto significa que hay tres algoritmos diferentes, dos de los cuales dan resultados exactos ( ^
y ^^
), mientras que **
dan resultados aproximados. Al elegir qué operador usar, elige qué algoritmo invocar.
^
requiere que su segundo argumento sea un Integral
. Si no me equivoco, la implementación puede ser más eficiente si sabe que está trabajando con un exponente integral. Además, si quieres algo como 2 ^ (1.234)
, aunque tu base sea una integral, 2, tu resultado obviamente será fraccionario. Tiene más opciones para que pueda tener un control más estricto sobre qué tipos entran y salen de su función de exponenciación.
El sistema de tipos de Haskell no tiene el mismo objetivo que otros sistemas de tipos, como C, Python o Lisp. La mecanografía de pato es (casi) lo opuesto a la mentalidad de Haskell.
class Duck a where quack :: a -> Quack
define lo que esperamos de un pato, y luego cada instancia especifica algo que puede comportarse como un pato.
Duck
.