51 votos

Exponenciación en Haskell

¿Alguien puede decirme por qué el Preámbulo de Haskell define dos funciones separadas para la exponenciación (es decir, ^ y **)? Pensé que el sistema de tipos estaba destinado a eliminar este tipo de duplicación.

Prelude> 2^2
4
Prelude> 4**0.5
2.0

85voto

Mikhail Glushenkov Puntos 10348

En realidad, existen tres operadores de exponenciación: (^), (^^) y (**). ^ es exponenciación con exponente entero no negativo, ^^ es exponenciación con exponente entero, y ** es exponenciación con 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 operaciones numéricas generalmente tienen el mismo tipo que el argumento de entrada. Pero no puedes elevar un Int a una potencia de punto flotante y obtener un resultado de tipo Int. Por lo tanto, el sistema de tipos te impide hacer esto: (1::Int) ** 0.5 produce un error de tipo. Lo mismo ocurre con (1::Int) ^^ (-1).

Otra forma de ver esto es: los tipos Num están cerrados bajo ^ (no necesitan tener un inverso multiplicativo), los tipos Fractional están cerrados bajo ^^, los tipos Floating están cerrados bajo **. Dado que no hay una instancia Fractional para Int, no puedes elevarlo a una potencia negativa.

Lo ideal sería que el segundo argumento de ^ estuviera estáticamente restringido a ser no negativo (actualmente, 1 ^ (-2) arroja una excepción en tiempo de ejecución). Pero no hay un tipo para números naturales en el Prelude.

23voto

augustss Puntos 15750

El sistema de tipos de Haskell no es lo suficientemente poderoso para expresar los tres operadores de exponenciación como uno solo. Lo que realmente desearías es algo como esto:

class Exp a b where (^) :: a -> b -> a
instance (Num a,        Integral b) => Exp a b where ... -- actual ^
instance (Fractional a, Integral b) => Exp a b where ... -- actual ^^
instance (Floating a,   Floating b) => Exp a b where ... -- actual **

Esto realmente no funciona incluso si activas la extensión de clase de tipo de varios parámetros, porque la selección de la instancia necesita ser más inteligente de lo que Haskell permite actualmente.

7voto

Gabe Puntos 49718

¡No define dos operadores, ¡define tres! Del informe:

Hay tres operaciones de exponenciación de dos argumentos: (^) eleva cualquier número a una potencia no negativa entera, (^^) eleva un número fraccional a cualquier potencia entera, y (**) toma dos argumentos de punto flotante. El valor de x^0 o x^^0 es 1 para cualquier x, incluido el cero; 0**y no está definido.

Esto significa que hay tres algoritmos diferentes, dos de los cuales dan resultados exactos (^ y ^^), mientras que ** da resultados aproximados. Al elegir qué operador usar, elige qué algoritmo invocar.

4voto

Dan Burton Puntos 26639

^ requiere que su segundo argumento sea de tipo Integral. Si no estoy equivocado, la implementación puede ser más eficiente si sabes que estás trabajando con un exponente integral. Además, si quieres algo como 2 ^ (1.234), aunque tu base sea un número entero, 2, tu resultado será obviamente fraccional. Tienes más opciones para tener un control más estricto sobre los tipos que entran y salen de tu función de exponenciación.

El sistema de tipos de Haskell no tiene el mismo objetivo que otros sistemas de tipos, como los de C, Python o Lisp. La escritura de patos es (casi) lo contrario de la mentalidad de Haskell.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X