Qualcuno può dirmi perché Haskell Prelude definisce due funzioni separate per l'elevamento a potenza (cioè ^
e **
)? Pensavo che il sistema di caratteri dovesse eliminare questo tipo di duplicazione.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Qualcuno può dirmi perché Haskell Prelude definisce due funzioni separate per l'elevamento a potenza (cioè ^
e **
)? Pensavo che il sistema di caratteri dovesse eliminare questo tipo di duplicazione.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Risposte:
In realtà ci sono tre operatori di elevazione a potenza: (^)
, (^^)
e (**)
. ^
è un esponenziamento integrale non negativo, ^^
è un esponenziamento intero ed **
è un esponenziazione in virgola mobile:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
Il motivo è la sicurezza del tipo: i risultati delle operazioni numeriche hanno generalmente lo stesso tipo degli argomenti di input. Ma non puoi elevare an Int
a una potenza in virgola mobile e ottenere un risultato di tipo Int
. E così il sistema di tipi ti impedisce di farlo: (1::Int) ** 0.5
produce un errore di tipo. Lo stesso vale per (1::Int) ^^ (-1)
.
Un altro modo per dirlo: i Num
tipi sono chiusi sotto ^
(non è necessario che abbiano un inverso moltiplicativo), i Fractional
tipi sono chiusi sotto ^^
, i Floating
tipi sono chiusi sotto **
. Poiché non esiste Fractional
un'istanza per Int
, non puoi elevarlo a un potere negativo.
Idealmente, il secondo argomento di ^
sarebbe staticamente vincolato a non essere negativo (attualmente, 1 ^ (-2)
genera un'eccezione in fase di esecuzione). Ma non esiste un tipo per i numeri naturali in Prelude
.
Il sistema di tipi di Haskell non è abbastanza potente per esprimere i tre operatori di esponenziazione come uno solo. Quello che vorresti davvero è qualcosa del genere:
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 **
Questo non funziona davvero anche se si attiva l'estensione di classe del tipo multiparametrico, perché la selezione dell'istanza deve essere più intelligente di quanto attualmente consentito da Haskell.
Int
e Integer
. Per essere in grado di avere queste tre dichiarazioni di istanza, la risoluzione dell'istanza deve utilizzare il backtracking e nessun compilatore Haskell lo implementa.
Non definisce due operatori - ne definisce tre! Dal rapporto:
Ci sono tre operazioni di esponenziazione a due argomenti: (
^
) eleva qualsiasi numero a una potenza intera non negativa, (^^
) solleva un numero frazionario a qualsiasi potenza intera e (**
) accetta due argomenti a virgola mobile. Il valore dix^0
ox^^0
è 1 per qualsiasix
, incluso zero;0**y
è indefinito.
Ciò significa che ci sono tre diversi algoritmi, due dei quali danno risultati esatti ( ^
e ^^
), mentre **
danno risultati approssimativi. Scegliendo quale operatore utilizzare, scegli quale algoritmo richiamare.
^
richiede che il suo secondo argomento sia un Integral
. Se non sbaglio, l'implementazione può essere più efficiente se sai di lavorare con un esponente integrale. Inoltre, se vuoi qualcosa di simile 2 ^ (1.234)
, anche se la tua base è un integrale, 2, il tuo risultato sarà ovviamente frazionario. Hai più opzioni in modo da avere un controllo più stretto su quali tipi entrano ed escono dalla tua funzione di esponenziazione.
Il sistema di tipi di Haskell non ha lo stesso obiettivo di altri sistemi di tipi, come C, Python o Lisp. La digitazione a papera è (quasi) l'opposto della mentalità Haskell.
class Duck a where quack :: a -> Quack
definisce cosa ci aspettiamo da un'anatra, quindi ogni istanza specifica qualcosa che può comportarsi come un'anatra.
Duck
.