2011-11-24 3 views
16

Perché è che posso fare:Perché non posso aggiungere un numero intero a Double in Haskell?

1 + 2.0 

ma quando provo:

let a = 1 
let b = 2.0 
a + b 

<interactive>:1:5: 
    Couldn't match expected type `Integer' with actual type `Double' 
    In the second argument of `(+)', namely `b' 
    In the expression: a + b 
    In an equation for `it': it = a + b 

Questo sembra semplicemente strano! Ti fa mai inciampare?

P.S .: So che "1" e "2.0" sono costanti polimorfiche. Questo non è ciò che mi preoccupa. Quello che mi preoccupa è perché haskell fa una cosa nel primo caso, ma un altro nel secondo!

+3

Come dice il meme, "ha bisogno di più' daIntegral' ". –

+1

Tipo di mi ricorda 'x.f()' vs 'g = x.f; g(); 'in Javascript. Fa schifo quando le variabili rompono l'astrazione del modello di sostituzione. – hugomg

risposta

7

È possibile utilizzare GHCI per saperne di più su questo. Utilizzare il comando :t per ottenere il tipo di un'espressione.

Prelude> :t 1 
1 :: Num a => a 

Quindi 1 è una costante che può essere qualsiasi tipo numerico (Double, Integer, ecc)

Prelude> let a = 1 
Prelude> :t a 
a :: Integer 

Quindi, in questo caso, Haskell dedotto il tipo concreto per a è Integer. Allo stesso modo, se si scrive let b = 2.0, Haskell deduce il tipo Double. Utilizzando let, Haskell ha inferto un tipo più specifico di (forse) era necessario, e questo porta al tuo problema. (Qualcuno con più esperienza di me può forse commentare sul perché questo è il caso.) Poiché ha tipo Num a => a -> a -> a, i due argomenti devono avere lo stesso tipo.

È possibile risolvere questo problema con la funzione fromIntegral:

Prelude> :t fromIntegral 
fromIntegral :: (Num b, Integral a) => a -> b 

Questa funzione converte i tipi interi ad altri tipi numerici.Ad esempio:

Prelude> let a = 1 
Prelude> let b = 2.0 
Prelude> (fromIntegral a) + b 
3.0 
+3

La [limitazione monomorfismo] (http://www.haskell.org/haskellwiki/Monomorphism_restriction) è la ragione per cui "let' fa scegliere un tipo specifico. Puoi sovrascriverlo dandogli una firma di tipo, o facendo ': set -XNoMonomorphismRestriction' in GHCi (o il flag equivalente durante la compilazione). – hammar

+0

È necessario impostare il flag quando si complimenta? Pensavo che il compilatore non facesse "default" ... – drozzy

+0

@drozzy: lo fa. GHCi ha solo regole di default leggermente diverse. In particolare, GHCi consente l'impostazione predefinita di un insieme più ampio di vincoli di classe e aggiunge '()' all'elenco dei tipi predefiniti. – hammar

19

La firma del tipo di (+) è definita come Num a => a -> a -> a, il che significa che funziona su qualsiasi membro della classe di caratteri Num, ma entrambi gli argomenti devono essere dello stesso tipo.

Il problema qui è con GHCI e l'ordine in cui stabilisce i tipi, non Haskell stesso. Se dovessi inserire uno dei tuoi esempi in un file (usando do per le espressioni let) esso si compilerebbe e funzionerebbe correttamente, poiché GHC userebbe l'intera funzione come contesto per determinare i tipi di letterali 1 e 2.0.

Tutto ciò che accade nel primo caso è che GHCI indovina i tipi di numeri che stai inserendo. Il più preciso è un Double, quindi si presuppone che l'altro debba essere un Double ed esegua il calcolo. Tuttavia, quando si utilizza l'espressione let, ha solo un numero su cui lavorare, quindi decide che 1 è un Integer e 2.0 è un Double.

MODIFICA: GHCI non è davvero "indovinando", sta utilizzando regole di default di tipo molto specifiche che sono definite nel report Haskell. Puoi leggere un po 'di più su questo here.

+3

Prima di tutto, ho upvoted. Ma visto che ho l'abitudine di fare il pelo nell'uovo: un sistema di tipo statico può supportare l'aggiunta di numeri di tipi diversi. È solo che la definizione definita da Haskell per ('(+) :: Num a => a -> a -> a') non lo consente. (Tuttavia, non funziona molto bene con l'inferenza di tipo, il che probabilmente è il motivo per cui non è stato eseguito.) Inoltre, la risposta potrebbe essere più chiara se esplicitamente ("indovina" non lo rende giustizia IMHO) spiega il tipo di default e il fatto che i letterali sono polimorfici. – delnan

+3

"Haskell ha un sistema di tipo statico, quindi non sarai mai in grado di aggiungere due tipi diversi insieme." Questo è un po 'di eccessiva semplificazione.Molte lingue hanno sistemi di tipo statico e consentono di aggiungere valori a doppio. La parola chiave qui è che Haskell non ha conversioni implicite. – sepp2k

+0

Sì, non mi sentivo come se avessi una buona padronanza del tipo di default e tale da spiegarlo. Sentiti libero di aggiungere la tua risposta con quella roba. –

11

I primi lavori perché numerici letterali sono polimorfici (. Sono interpretati come fromInteger literal risp fromRational literal), quindi in 1 + 2.0, si hanno davvero fromInteger 1 + fromRational 2, in assenza di altri vincoli, il tipo di risultato il valore predefinito è Double.

Il secondo non funziona a causa della limitazione del monomorfismo. Se si associa qualcosa senza una firma di tipo e con un semplice motivo (name = expresion), a quell'entità viene assegnato un tipo monomorfico. Per il valore letterale 1, abbiamo un vincolo Num, pertanto, in base allo defaulting rules, il suo tipo è impostato su nell'associazione let a = 1. Analogamente, il tipo di letterale frazionario è impostato per default su Double.

Funzionerà, tra l'altro, se si :set -XNoMonomorphismRestriction in ghci.

Il motivo della limitazione del monomorfismo è di impedire la perdita di condivisione, se vedi un valore che assomiglia a una costante, non ti aspetti che venga calcolato più di una volta, ma se avesse un tipo polimorfico, sarebbe ricalcolato ogni volta che viene utilizzato.

3

Altri hanno affrontato molti aspetti di questa domanda abbastanza bene. Vorrei dire una parola sulla logica alla base del motivo per cui + ha la firma del tipo Num a => a -> a -> a.

In primo luogo, la classe di caratteri Num non ha modo di convertire un'istanza artbitrary di Num in un'altra. Supponiamo che abbia un tipo di dati per numeri immaginari; sono ancora numeri, ma non puoi davvero convertirli correttamente in un solo Int.

In secondo luogo, quale tipo di firma preferiresti?

(+) :: (Num a, Num b) => a -> b -> a 
(+) :: (Num a, Num b) => a -> b -> b 
(+) :: (Num a, Num b, Num c) => a -> b -> c 

Dopo aver considerato le altre opzioni, si rendono conto che a -> a -> a è la scelta più semplice. I risultati polimorfici (come nel terzo suggerimento sopra) sono interessanti, ma a volte possono essere troppo generici per essere usati convenientemente.

In terzo luogo, Haskell non è Blub. La maggior parte, anche se forse non tutte, le decisioni di design su Haskell non tengono conto delle convenzioni e delle aspettative dei linguaggi popolari. Mi piace spesso dire che il primo passo per imparare Haskell è disimparare prima tutto ciò che pensi di sapere sulla programmazione. Sono sicuro che molti, se non tutti, gli esperti di Haskellers sono stati inciampati dalla classe numerica e da varie altre curiosità di Haskell, perché la maggior parte ha imparato prima un linguaggio più "mainstream". Ma sii paziente, alla fine raggiungerai il nirvana Haskell. :)

Problemi correlati