2013-10-08 18 views
12

Dopo un po 'di armeggiare con un generatore di numeri casuali, sono giunto alla conclusione che la mia comprensione del sistema di tipo Haskell è incompleta, se non del tutto mancante.Come posso migliorare la mia comprensione del sistema Haskell Type?

Ecco un esempio. Sto cercando di generare un flusso di orari degli eventi di Poisson:

import System.Random 
import Numeric 

bround :: (RealFloat r, Integral b) => b -> r -> r 
bround places x = (fromIntegral (round (x * exp)))/exp 
     where exp = 10.0^places 

rndp = (bround 4) 

myGen = (mkStdGen 1278267) 

infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) => r -> r -> g -> [r] 
infinitePoissonStream rate start gen = next:(infinitePoissonStream rate next newGen) 
     where (rvalue, newGen) = random gen 
       next = (start - log(rvalue)/rate) 

printAll :: (RealFloat r) => [r] -> IO() 
printAll []  = return() 
printAll (x:xs) = do putStrLn (showFFloat (Just 8) x "") 
        printAll xs 

main = do 
     printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen)) 

che mi rimprovera così:

mwe3.hs:23:8: 
    No instance for (RealFloat r0) arising from a use of `printAll' 
    The type variable `r0' is ambiguous 
    Possible fix: add a type signature that fixes these type variable(s) 
    Note: there are several potential instances: 
     instance RealFloat Double -- Defined in `GHC.Float' 
     instance RealFloat Float -- Defined in `GHC.Float' 
     instance RealFloat Foreign.C.Types.CDouble 
     -- Defined in `Foreign.C.Types' 
     ...plus one other 
    In a stmt of a 'do' block: 
     printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen)) 
    In the expression: 
     do { printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen)) } 
    In an equation for `main': 
     main 
      = do { printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen)) } 

mwe3.hs:23:27: 
    No instance for (Random r0) 
     arising from a use of `infinitePoissonStream' 
    The type variable `r0' is ambiguous 
    Possible fix: add a type signature that fixes these type variable(s) 
    Note: there are several potential instances: 
     instance Random Bool -- Defined in `System.Random' 
     instance Random Foreign.C.Types.CChar -- Defined in `System.Random' 
     instance Random Foreign.C.Types.CDouble 
     -- Defined in `System.Random' 
     ...plus 33 others 
    In the second argument of `take', namely 
     `(infinitePoissonStream 1.0 0.0 myGen)' 
    In the first argument of `printAll', namely 
     `(take 10 (infinitePoissonStream 1.0 0.0 myGen))' 
    In a stmt of a 'do' block: 
     printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen)) 

mwe3.hs:23:49: 
    No instance for (Fractional r0) arising from the literal `1.0' 
    The type variable `r0' is ambiguous 
    Possible fix: add a type signature that fixes these type variable(s) 
    Note: there are several potential instances: 
     instance Fractional Double -- Defined in `GHC.Float' 
     instance Fractional Float -- Defined in `GHC.Float' 
     instance Integral a => Fractional (GHC.Real.Ratio a) 
     -- Defined in `GHC.Real' 
     ...plus two others 
    In the first argument of `infinitePoissonStream', namely `1.0' 
    In the second argument of `take', namely 
     `(infinitePoissonStream 1.0 0.0 myGen)' 
    In the first argument of `printAll', namely 
     `(take 10 (infinitePoissonStream 1.0 0.0 myGen))' 

Dopo aver frugato, ho "fisso" cambiando l'ultima riga:

printAll (take 10 (infinitePoissonStream 1.0 0.0 myGen) :: [Double]) 

Ora, ho voluto usare l'aritmetica limitata precisione, così ho cambiato la linea "Avanti" per questo:

  next = rndp (start - log(rvalue)/rate) 

e ora non riesce così:

mwe3.hs:15:29: 
    Could not deduce (r ~ Double) 
    from the context (RandomGen g, Random r, RealFloat r) 
     bound by the type signature for 
       infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) => 
              r -> r -> g -> [r] 
     at mwe3.hs:12:26-83 
     `r' is a rigid type variable bound by 
      the type signature for 
      infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) => 
            r -> r -> g -> [r] 
      at mwe3.hs:12:26 
    In the first argument of `(-)', namely `start' 
    In the first argument of `rndp', namely 
     `(start - log (rvalue)/rate)' 
    In the expression: rndp (start - log (rvalue)/rate) 

Così sto cominciando a giungere alla conclusione che io davvero non so quello che sto facendo. Quindi:

  1. Qualcuno può spiegare cosa mi manca qui?
  2. Eventuali puntatori a capitolo e verso in cui potrei avere una possibilità di comprendere i principi di base?

risposta

14

Il problema qui è che GHC non è in grado di determinare automaticamente quale RealFloat si desidera utilizzare. Hai codificato tutto in termini di RealFloat e in main non hai fornito un tipo concreto per l'utilizzo, quindi si interrompe e dice "non riuscivo a capirlo". È possibile risolvere questo o cambiando di almeno uno dei tipi di firme per usare Float o Double specifico, ma la soluzione migliore è quella di indicare solo che tipo si suppone di essere in main, in questo modo:

main = printAll $ take 10 (infinitePoissonStream 1.0 0.0 myGen :: [Double]) 

Quando si aggiungi [Double] a questa linea, stai dicendo esplicitamente a GHC quale tipo utilizzare durante il runtime. Senza di esso, sa solo di utilizzare RealFloat r e Random r e ci sono più tipi tra cui scegliere, vale a dire Float e Double. O funzionerà per questo caso, ma il compilatore non lo sa.

Inoltre, vorrei suggerire alcuni cambiamenti stilistici per sbarazzarsi di alcune di queste parentesi:

import System.Random 
import Numeric 

bround :: (RealFloat r, Integral b) => b -> r -> r 
bround places x = fromIntegral (round $ x * e)/e 
     where e = 10.0^places 
     -- exp is a pre-defined function, shouldn't name a variable with it 

-- Even if it's trivial, you should add type signatures, it really helps others read your code faster 
rndp = bround 4 
myGen = mkStdGen 1278267 

-- function application before operator application means you can remove some parens 
infinitePoissonStream :: (RandomGen g, Random r, RealFloat r) => r -> r -> g -> [r] 
infinitePoissonStream rate start gen = next : infinitePoissonStream rate next newGen 
     where (rvalue, newGen) = random gen 
       next = start - log rvalue/rate 

-- Start a new line at the beginning of a do block, the indentations are nicer 
printAll :: (RealFloat r) => [r] -> IO() 
printAll []  = return() 
printAll (x:xs) = do 
    putStrLn $ showFFloat (Just 8) x "" 
    printAll xs 

-- No need for a do block with only one statement 
main = printAll $ take 10 (infinitePoissonStream 1.0 0.0 myGen :: [Double]) 

Questi cambiamenti per lo più da hlint.

+1

Grazie (+1). Un altro, virtuale, +1 per il consiglio stilistico, e le mie scuse a tutti coloro i cui occhi sono stati feriti dal mio stile di codifica. Dalla tua risposta deduco, forse erroneamente, che potrebbe essere meglio abbandonare del tutto l'uso della classe RealFloat e andare direttamente a 'Double' al posto di' r'. È giusto? –

+3

@ Brent.Longborough Per lo più ho incollato il codice nel mio editor e ho evidenziato dove potresti liberarti di parentesi che non stavano facendo nulla. Anche il newline-at-do-block è solo uno dei miei animaletti, ma il tuo stile non era particolarmente offensivo. Per quanto riguarda i tipi, direi che solo tu puoi sapere se hai bisogno della flessibilità di 'RealFloat'. Se prevedi di usarlo per tipi diversi da "Double", mantienilo così com'è. Se la userai solo con 'Double', cambia semplicemente la firma per usare esplicitamente' Double'. – bheklilr

11

Per quanto riguarda come si può imparare di più su come eseguire il debug di questo tipo di problema, ecco un trucco che mi ha aiutato immensamente. Ogni volta che sono completamente sconcertato da un messaggio come questo, faccio quanto segue:

  • Se c'è una firma del tipo sulla funzione in questione, rimuoverla e vedere se qualcosa cambia. Se viene compilato, chiedere a ghci quale sia il tipo (utilizzando :t). Se non viene compilato, almeno il messaggio di errore potrebbe essere abbastanza diverso da darti un altro indizio.
  • Se non è presente una firma del tipo, aggiungerne uno. Anche se non viene compilato, il messaggio di errore potrebbe darti un altro indizio.
  • Se ciò non aiuta, aggiungere temporaneamente dichiarazioni di tipo su ciascuna espressione nella funzione. (Spesso è necessario suddividere alcune espressioni per vedere cosa sta realmente accadendo. Potrebbe anche essere necessario abilitare temporaneamente il pragma ScopedTypeVariables.) Compilare nuovamente e controllare i messaggi di errore.

Quest'ultimo è più lavoro, ma ho imparato molto da quell'esercizio. Solitamente individua il punto esatto in cui c'è una discrepanza tra quello che penso sia il tipo e ciò che GHC pensa che sia il tipo.

Se dovessi fare quello scorso sul tuo codice, le modifiche potrebbero essere qualcosa del genere. Si noti che l'errore ora punta alla funzione main invece della funzione printAll, che ci aiuta a capire dove risolverlo.

printAll :: (RealFloat r) => [r] -> IO() 
printAll []  = return() 
printAll (x:xs) = do 
    let temp1=showFFloat (Just 8) x "" :: String 
    putStrLn temp1 :: IO() 
    printAll xs :: IO() 

main = do 
    let temp2 = take 10 (infinitePoissonStream 1.0 0.0 myGen) :: (RealFloat r) => [r] 
    -- but if you make this change, it compiles: 
    -- let temp2 = take 10 (infinitePoissonStream 1.0 0.0 myGen) :: [Double] 
    printAll temp2 

E, naturalmente, una volta che posso correggere l'errore di compilazione, poi prendo un'altra occhiata al messaggio di errore originale per vedere se posso capire ora.

Problemi correlati