Esponenziazione in Haskell


91

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:


130

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 Inta 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.5produce un errore di tipo. Lo stesso vale per (1::Int) ^^ (-1).

Un altro modo per dirlo: i Numtipi sono chiusi sotto ^(non è necessario che abbiano un inverso moltiplicativo), i Fractionaltipi sono chiusi sotto ^^, i Floatingtipi sono chiusi sotto **. Poiché non esiste Fractionalun'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.


31

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.


4
L'affermazione secondo cui questo non è implementabile è ancora vera? IIRC, haskell ha un'opzione per il secondo parametro di una classe di tipo multiparametro da determinare rigorosamente dal primo parametro. C'è un altro problema oltre a questo che non può essere risolto?
RussellStewart

2
@singular È ancora vero. Il primo argomento non determina il secondo, ad esempio, vuoi che l'esponente sia sia Inte 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.
agosto

7
L' argomento "il sistema di tipi non è abbastanza potente" è ancora valido a marzo 2015?
Erik Kaplun

3
Certamente non puoi scriverlo nel modo in cui suggerisco, ma potrebbe esserci un modo per codificarlo.
agosto

2
@ErikAllik probabilmente lo fa per Haskell standard, poiché nessun nuovo rapporto Haskell è uscito dal 2010.
Martin Capodici

10

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 di x^0o x^^0è 1 per qualsiasi x, 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.


4

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


4
Non sono totalmente d'accordo sul fatto che la mentalità del tipo Haskell sia l'opposto della digitazione a papera. Le classi di tipo Haskell sono molto simili alla digitazione a papera. class Duck a where quack :: a -> Quackdefinisce cosa ci aspettiamo da un'anatra, quindi ogni istanza specifica qualcosa che può comportarsi come un'anatra.
agosto

9
@augustss Vedo da dove vieni. Ma il motto informale alla base della dattilografia è "se sembra un'anatra, si comporta come un'anatra e fa il ciarlatano come un'anatra, allora è un'anatra". In Haskell non è un'anatra a meno che non sia dichiarata un'istanza di Duck.
Dan Burton

1
È vero, ma è quello che mi aspetto da Haskell. Puoi fare qualsiasi cosa tu voglia un'anatra, ma devi essere esplicito al riguardo. Non vogliamo scambiare qualcosa che non abbiamo chiesto per essere un'anatra.
agosto

C'è una differenza più specifica tra il modo Haskell di fare le cose e la dattilografia. Sì, puoi dare a qualsiasi tipo la classe Duck ma non è un Duck. È capace di starnazzare, certo, ma è comunque, concretamente, di qualunque tipo fosse. Non puoi ancora avere un elenco di anatre. Una funzione che accetta un elenco di anatre e mescola e abbina vari tipi di anatre di classe non funzionerà. A questo proposito Haskell non ti permette di dire semplicemente "Se ciarlatano come un'anatra, allora è un'anatra". In Haskell, tutte le tue anatre devono essere Quackers dello stesso tipo. Questo è molto diverso dalla digitazione anatra in effetti.
mmachenry

Puoi avere un elenco di anatre miste, ma hai bisogno dell'estensione della quantificazione esistenziale.
Bolpat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.