2011-06-19 13 views
72

Qualcuno può dirmi perché il Preludio Haskell definisce due funzioni separate per il fattore di elevazione a potenza (ad esempio ^ e **)? Pensavo che il sistema tipo avrebbe dovuto eliminare questo tipo di duplicazione.Esponenziazione in Haskell

Prelude> 2^2 
4 
Prelude> 4**0.5 
2.0 

risposta

107

Ci sono in realtà tre operatori di elevazione a potenza: (^), (^^) e (**). elevamento integrante ^ è non negativo, ^^ è elevamento intero e ** è virgola mobile a potenza:

(^) :: (Num a, Integral b) => a -> b -> a 
(^^) :: (Fractional a, Integral b) => a -> b -> a 
(**) :: Floating a => a -> a -> a 

La ragione è di tipo sicurezza: risultati delle operazioni numeriche hanno generalmente lo stesso tipo come argomento ingresso (s) . Ma non è possibile aumentare un valore di Int in virgola mobile e ottenere un risultato di tipo Int. E così il sistema di tipi ti impedisce di fare questo: (1::Int) ** 0.5 produce un errore di tipo. Lo stesso vale per (1::Int) ^^ (-1).

Un altro modo per mettere questa: Num tipi sono chiusi rispetto ^ (non sono tenuti ad avere un inverso moltiplicativo), Fractional tipi sono chiusi sotto ^^, Floating tipi sono chiusi rispetto **. Poiché non è presente l'istanza Fractional per Int, non è possibile aumentarla a una potenza negativa.

Idealmente, il secondo argomento di ^ sarebbe staticamente vincolato per essere non negativo (al momento, 1^(-2) genera un'eccezione run-time). Ma non esiste un tipo per i numeri naturali nel Prelude.

8

Non definisce due operatori: ne definisce tre! Dalla relazione:

Ci sono tre operazioni di elevazione a potenza a doppio argomento: (^) solleva qualsiasi numero a potenza intero non negativo, (^^) solleva un numero frazionario di qualsiasi potenza intera, e (**) prende due galleggianti -point argomenti. Il valore di x^0 o x^^0 è 1 per qualsiasi x, compreso zero; 0**y non è definito.

Questo significa che ci sono tre differenti algoritmi, due dei quali danno risultati esatti (^ e ^^), mentre ** dà risultati approssimati. Scegliendo quale operatore usare, scegli quale algoritmo richiamare.

4

^ richiede che il secondo argomento sia Integral. Se non sbaglio, l'implementazione può essere più efficiente se si sa che si sta lavorando con un esponente integrale. Inoltre, se vuoi qualcosa come 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 tipo Haskell non ha lo stesso obiettivo di altri sistemi di tipi, come C, Python o Lisp. La digitazione di anatra è (quasi) l'opposto della mentalità di Haskell.

+4

Non sono completamente d'accordo sul fatto che la mentalità di tipo Haskell sia l'opposto della digitazione anatra. Le classi di tipo Haskell sono molto simili alla digitazione anatra. 'classe Duck a where quack :: a -> Quack' definisce cosa ci aspettiamo da un'anatra, e quindi ogni istanza specifica qualcosa che può comportarsi come un'anatra. – augustss

+7

@augusts Vedo da dove vieni. Ma il motto informale dietro la digitazione anatra è "se sembra un'anatra, si comporta come un'anatra, e ciarlona come un'anatra, quindi è un'anatra". In Haskell non è un'anatra a meno che non sia dichiarata un'istanza di "Anatra". –

+1

È vero, ma è quello che mi aspetterei da Haskell. Puoi fare tutto ciò che vuoi, ma devi essere esplicito a riguardo. Non vogliamo scambiare qualcosa che non abbiamo chiesto di essere un'anatra. – augustss

26

Il sistema di tipo Haskell non è abbastanza potente da esprimere i tre operatori di esponenziazione come uno.Quello che ci si vuole veramente è qualcosa di simile:

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 in realtà non funziona, anche se si attiva l'estensione tipo di classe multi-parametro, perché la selezione istanza deve essere più intelligente di Haskell consente attualmente .

+3

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

+1

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

+4

Il _ "sistema di tipo non è abbastanza potente" _ argomento ancora valido a marzo 2015? –