Alguém pode me dizer por que o Prelúdio de Haskell define duas funções separadas para exponenciação (ou seja, ^
e **
)? Achei que o sistema de tipos deveria eliminar esse tipo de duplicação.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Alguém pode me dizer por que o Prelúdio de Haskell define duas funções separadas para exponenciação (ou seja, ^
e **
)? Achei que o sistema de tipos deveria eliminar esse tipo de duplicação.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Respostas:
Na verdade, existem três operadores de exponenciação: (^)
, (^^)
e (**)
. ^
é exponenciação integral não negativa, ^^
é exponenciação inteira e **
é exponenciação de ponto flutuante:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
A razão é a segurança de tipo: resultados de operações numéricas geralmente têm o mesmo tipo que o (s) argumento (s) de entrada. Mas você não pode elevar um Int
a uma potência de ponto flutuante e obter um resultado do tipo Int
. E então o sistema de tipos impede que você faça isso: (1::Int) ** 0.5
produz um erro de tipo. O mesmo vale para (1::Int) ^^ (-1)
.
Outra forma de colocar isso: Num
tipos são fechados em ^
(eles não precisam ter um inverso multiplicativo), Fractional
tipos são fechados em ^^
, Floating
tipos são fechados em **
. Como não há Fractional
instância para Int
, você não pode elevá-lo a uma potência negativa.
Idealmente, o segundo argumento de ^
seria estaticamente restrito para ser não negativo (atualmente, 1 ^ (-2)
lança uma exceção de tempo de execução). Mas não há nenhum tipo para números naturais no Prelude
.
O sistema de tipos de Haskell não é poderoso o suficiente para expressar os três operadores de exponenciação como um. O que você realmente deseja é algo assim:
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 **
Isso realmente não funciona mesmo se você ativar a extensão de classe de tipo multiparâmetro, porque a seleção de instância precisa ser mais inteligente do que o permitido atualmente por Haskell.
Int
e Integer
. Para poder ter essas três declarações de instância, a resolução da instância precisa usar backtracking, e nenhum compilador Haskell implementa isso.
Não define dois operadores - define três! Do relatório:
Existem três operações de exponenciação de dois argumentos: (
^
) aumenta qualquer número para uma potência inteira não negativa, (^^
) aumenta um número fracionário para qualquer potência inteira e (**
) leva dois argumentos de ponto flutuante. O valor dex^0
oux^^0
é 1 para qualquerx
, incluindo zero;0**y
é indefinido.
Isso significa que há três algoritmos diferentes, dois dos quais fornecem resultados exatos ( ^
e ^^
), enquanto **
fornece resultados aproximados. Ao escolher qual operador usar, você escolhe qual algoritmo invocar.
^
requer que seu segundo argumento seja um Integral
. Se não me engano, a implementação pode ser mais eficiente se você souber que está trabalhando com um expoente integral. Além disso, se você quiser algo como 2 ^ (1.234)
, mesmo que sua base seja uma integral, 2, seu resultado obviamente será fracionário. Você tem mais opções para ter um controle mais rígido sobre quais tipos entram e saem de sua função de exponenciação.
O sistema de tipos de Haskell não tem o mesmo objetivo de outros sistemas de tipos, como C's, Python's ou Lisp's. A digitação em pato é (quase) o oposto da mentalidade Haskell.
class Duck a where quack :: a -> Quack
define o que esperamos de um pato e, em seguida, cada instância especifica algo que pode se comportar como um pato.
Duck
.