length - haskell exponential function
Exponenciación en Haskell (4)
¿Puede alguien decirme por qué Haskell Prelude define dos funciones separadas para la exponenciación (es decir, ^
y **
)? Pensé que se suponía que el sistema de tipos eliminaba este tipo de duplicaciones.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
El sistema de tipos de Haskell no es lo suficientemente potente como para expresar los tres operadores de exponenciación como uno solo. Lo que realmente quieres 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 multiparámetro, porque la selección de instancia debe ser más inteligente de lo que permite actualmente Haskell.
En realidad, hay tres operadores de exponenciación: (^)
, (^^)
y (**)
. ^
es exponenciación integral no negativa, ^^
es una exponenciación entera, y **
es exponenciación de coma flotante:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
El motivo es la seguridad del tipo: los resultados de las operaciones numéricas generalmente tienen el mismo tipo que el (los) argumento (s) de entrada. Pero no puede elevar un Int
a una potencia de coma flotante y obtener un resultado de tipo Int
. Y entonces el sistema de tipo le impide hacer esto: (1::Int) ** 0.5
produce un error de tipo. Lo mismo ocurre con (1::Int) ^^ (-1)
.
Otra forma de decir esto: los tipos Num
se cierran bajo ^
(no se requiere que tengan un inverso multiplicativo), los tipos Fractional
se cierran bajo ^^
, Floating
tipos Floating
se cierran bajo **
. Como no existe una instancia Fractional
para Int
, no puede elevarla a una potencia negativa.
Idealmente, el segundo argumento de ^
estaría estáticamente restringido para no ser negativo (actualmente, 1 ^ (-2)
arroja una excepción en tiempo de ejecución). Pero no hay ningún tipo para números naturales en el Prelude
.
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 cualquierx
, incluido 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, usted 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 integral, 2, tu resultado será obviamente fraccionario. Tiene más opciones para que pueda tener un control más estricto sobre los tipos que entran y salen de su función de exponenciación.
El sistema de tipos de Haskell no tiene el mismo objetivo que otros sistemas tipo, como C, Python o Lisp. Duck typing es (casi) lo opuesto a la mentalidad de Haskell.