Exponenciación en Haskell

91

¿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
skytreebird
fuente

Respuestas:

130

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 Intpotencia de punto flotante y obtener un resultado de tipo Int. Y así, el sistema de tipos le impide hacer esto: (1::Int) ** 0.5produce un error de tipo. Lo mismo vale para (1::Int) ^^ (-1).

Otra forma de decir esto: los Numtipos están cerrados bajo ^(no es necesario que tengan un inverso multiplicativo), los Fractionaltipos están cerrados bajo ^^, los Floatingtipos están cerrados bajo **. Dado que no existe una Fractionalinstancia 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.

Mikhail Glushenkov
fuente
31

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.

augusts
fuente
4
¿Sigue siendo cierta la afirmación de que esto no se puede implementar? IIRC, haskell tiene una opción para que el segundo parámetro de una clase de tipo multiparámetro sea determinado estrictamente por el primer parámetro. ¿Hay otro problema más allá de este que no se pueda resolver?
RussellStewart
2
@singular Sigue siendo cierto. El primer argumento no determina el segundo, por ejemplo, desea que el exponente sea tanto Inty 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.
agosto de 2014
7
¿El argumento "el sistema de tipos no es lo suficientemente potente" se mantiene en marzo de 2015?
Erik Kaplun
3
Ciertamente no puede escribirlo de la manera que sugiero, pero podría haber alguna forma de codificarlo.
2015
2
@ErikAllik probablemente lo haga para Haskell estándar, ya que no ha salido ningún informe Haskell nuevo desde 2010.
Martin Capodici
10

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 de x^0o x^^0es 1 para cualquiera x, incluido cero; 0**yes 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.

Gabe
fuente
4

^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.

Dan Burton
fuente
4
No estoy totalmente de acuerdo en que la mentalidad de tipo Haskell sea lo opuesto a la mecanografía de pato. Las clases de tipos de Haskell se parecen mucho a la escritura de pato. class Duck a where quack :: a -> Quackdefine lo que esperamos de un pato, y luego cada instancia especifica algo que puede comportarse como un pato.
2011
9
@augustss Veo de dónde vienes. Pero el lema informal detrás de la escritura de pato es "si parece un pato, actúa como un pato y grazna como un pato, entonces es un pato". En Haskell no es un pato a menos que se declare una instancia de Duck.
Dan Burton
1
Eso es cierto, pero eso es lo que esperaría de Haskell. Puedes hacer lo que quieras con un pato, pero tienes que ser explícito al respecto. No queremos confundir algo que no pedimos con un pato.
2011
Hay una diferencia más específica entre la forma de hacer las cosas de Haskell y la escritura pato. Sí, puedes darle a cualquier tipo la clase Pato pero no es Pato. Es capaz de graznar, claro, pero aún así, concretamente, sea del tipo que sea. Aún no puedes tener una lista de patos. Una función que acepta una lista de patos y mezcla y empareja varios tipos de clase de pato no funcionará. En este sentido, Haskell no te permite decir simplemente "Si grazna como un pato, entonces es un pato". En Haskell, todos tus patos deben ser Quackers del mismo tipo. De hecho, esto es bastante diferente de la mecanografía de pato.
mmachenry
Puede tener una lista de patos mixtos, pero necesita la extensión de Cuantificación existencial.
Bolpat