2011-11-21 12 views
7

Provo a fare un semplice contatore. I miei contatori non salgono comunque. Mi sembra come se fossero re-inizializzati ogni volta dalla funzione "inc" o forse il (n + 1) non funziona. Come posso risolvere questo problema?I contatori vengono inizializzati ogni volta?

inc :: Int -> IO Int 
inc n = return (n+1) 

main :: IO() 
main = do 
    let c = 0 
    let f = 0 
    putStrLn "Starting..." 
    conn <- connect "192.168.35.62" 8081 
    time $ 
    forM_ [0..10000] $ \i -> do 
     p <- ping conn "ping" 
     if p=="pong" then inc c 
     else inc f 
    printf "Roundtrips %d\n" (c::Int) 

risposta

21

Sebbene le variabili mutabili possano essere utilizzate in Haskell come mostrato da altri commentatori, non è un buon stile: la mutazione non dovrebbe essere utilizzata nella maggior parte dei casi.

La funzione inc accetta il suo argomento in base al valore, ovvero non modifica il suo argomento. Inoltre, le variabili dichiarate da let mantengono i loro valori iniziali, quindi non è possibile cambiarle.

Come si scrive se nessuna variabile può essere modificata? La risposta è:

  1. invece di modificare qualcosa sul posto, restituisce un nuovo valore
  2. cicli for, uso ricorsione

Fortunatamente, è raramente necessario scrivere ricorsione te stesso, come la maggior parte ricorsiva i modelli sono già nella libreria standard.

Nel tuo caso è necessario eseguire diverse azioni IO e restituire il valore finale dei due contatori. Partiamo da una sola azione:

let tryOnePing (c, f) i = do 
    p <- ping conn "ping" 
    return $ if p == "pong" then (c+1, f) else (c, f+1) 

Qui dichiarare una funzione locale con 2 parametri: i valori correnti dei contatori, confezionati in una tupla (Int, Int) (una struttura in altre lingue) e l'iterazione corrente Int. La funzione esegue azioni IO e restituisce i valori modificati dei contatori IO (Int, Int). Tutto questo è indicato nel suo genere:

tryOnePing :: (Int, Int) -> Int -> IO (Int, Int) 

ping restituisce valore IO String tipo. Per confrontarlo, è necessario un String senza IO.Per fare questo, è necessario utilizzare >>= funzione:

let tryOnePing (c, f) i = ping conn "ping" >>= \p -> {process the string somehow} 

Poiché questo modello è comune, può essere scritto così

let tryOnePing (c, f) i = do 
    p <- ping conn "ping" 
    {process the string somehow} 

Ma il significato è esattamente lo stesso (compilatore traduce do notazione in applicazioni di >>=).

L'elaborazione mostra un paio di modelli comuni:

if p == "pong" then (c+1, f) else (c, f+1) 

Qui if non è un imperativo if ma più come un ternario condition ? value1 : value2 operatore in altre lingue. Si noti inoltre che la nostra funzione tryOnePing accetta (c, f) e restituisce (c+1, f) o (c, f+1). Abbiamo usato le tuple perché abbiamo bisogno di lavorare solo con 2 contatori. In caso di un numero elevato di contatori, dovremmo dichiarare un tipo di struttura e utilizzare i campi con nome.

Il valore dell'intero Se il costrutto è una tupla (Int, Int). ping è un'azione IO, quindi tryOnePing deve essere anch'essa un'azione IO. La funzione return non è un ritorno obbligatorio ma un modo per convertire (Int, Int) in IO (Int, Int).

Quindi, come abbiamo provato conOnePing, abbiamo bisogno di scrivere un ciclo per eseguirlo 1000 volte. Il tuo forM_ non era una buona scelta:

  1. E non passa i nostri due contatori tra iterazioni
  2. _ indica che getta il valore finale dei contatori via, invece di restituirla

È bisogno di qui non forM_ ma foldM

foldM tryOnePing (0, 0) [0 .. 10000] 

foldM esegue un'azione IO parametrizzata da ogni elemento della lista e passa uno stato tra le iterazioni, nel nostro caso i due contatori. Accetta lo stato iniziale e restituisce lo stato finale. Naturalmente, come le azioni dei suoi esegue IO, restituisce IO (Int, Int), quindi abbiamo bisogno di usare >>= per estrarre di nuovo per la visualizzazione:

foldM tryOnePing (0, 0) [0 .. 10000] >>= \(c, f) -> print (c, f) 

In Haskell, è possibile eseguire i cosiddetti 'riduzioni eta' , ovvero puoi rimuovere gli stessi identificatori da entrambi i lati di una dichiarazione di funzione. Per esempio. \foo -> bar foo è lo stesso di bar. Quindi, in questo caso con >>= si può scrivere:

foldM tryOnePing (0, 0) [0 .. 10000] >>= print 

che è molto più breve di do notazione:

do 
    (c, f) <- foldM tryOnePing (0, 0) [0 .. 10000] 
    print (c, f) 

Si noti inoltre che non è necessario disporre di due contatori: se si dispone di 3000 successi allora hai 7000 fallimenti. Quindi il codice diventa:

main = do 
    conn <- connect "192.168.35.62" 8081 
    let tryOnePing c i = do 
     p <- ping conn "ping" 
     return $ if p == "pong" then c+1 else c 
    c <- foldM tryOnePing 0 [0 .. 10000] 
    print (c, 10000 - c) 

Infine, in Haskell è opportuno separare le azioni IO dal codice non IO.Quindi è meglio per raccogliere tutti i risultati ping in una lista e poi a contare ping di successo in esso:

main = do 
    conn <- connect "192.168.35.62" 8081 
    let tryOnePing i = ping conn "ping" 
    pings <- mapM tryOnePing [0 .. 10000] 
    let c = length $ filter (\ping -> ping == "pong") pings 
    print (c, 10000 - c) 

noti che abbiamo evitato l'incremento del tutto.

Può essere scritto anche più breve, ma richiede più capacità di lettura e scrittura. Non ti preoccupare, imparerai presto questi trucchi:

main = do 
    conn <- connect "192.168.35.62" 8081 
    c <- fmap (length . filter (== "pong")) $ mapM (const $ ping conn "ping") [0 .. 10000] 
    print (c, 10000 - c) 
+0

Grazie alla mia" difesa "Devo dire che so dell'immutabilità, tuttavia ho trovato il" n + 1 "qui: http://hackage.haskell.org/packages/archive/mtl/1.1.0.2/doc/html/Control-Monad-State-Lazy.html e qui http://www.haskell.org/tutorial /goodies.html e si chiedeva se potesse funzionare. Da quando è stato compilato ht, - wooow, funziona. Queste zecche mirano a risolvere lo stesso problema, contatori che è? –

+0

Grazie. A proposito: quando sincronizzo il frammento della risposta n. 1 con IORefs ottengo la metà del tempo di esecuzione rispetto ai tempi 'ping <- time (mapM tryOnePing [0 .. 1000000])'. Presumo che nel primo caso non aspetti il ​​tempo di attesa per le risposte nei tempi. Potrebbe questo dover fare la natura pigra o potrebbe essere che questo costrutto IORef funzioni più velocemente ?? –

+0

Potrebbe essere entrambi. Utilizza la profilazione e/o guarda il linguaggio di haskell di livello inferiore generato da vedere. Usa anche -O2 dato che il mio codice più breve dipende fortemente dalla presenza di ottimizzazioni. – nponeccop

7

In Haskell i dati sono immutabili per impostazione predefinita. Ciò significa che lo c in inc c è sempre zero.

Per ottenere variabili mutabili in Haskell, è necessario richiederle in modo esplicito, ad esempio utilizzando IORefs. Il loro utilizzo si potrebbe scrivere qualcosa di simile:

import Data.IORef 

inc :: IORef Int -> IO() 
inc ref = modifyIORef ref (+1) 

main :: IO() 
main = do 
    c <- newIORef 0 
    f <- newIORef 0 
    putStrLn "Starting..." 
    conn <- connect "192.168.35.62" 8081 
    time $ 
    forM_ [0..10000] $ \i -> do 
     p <- ping conn "ping" 
     if p=="pong" 
     then inc c 
     else inc f 
    c' <- readIORef c 
    printf "Roundtrips %d\n" c' 
+0

Grazie! Che funzioni. Leggerò su questo. Un altro problema è che il codice riporta un runtime di ad es. 2sec ma esegue 15 secondi fino a quando non ritorna alla console :-(., Ma grazie per la vostra soluzione, questo è un grande aiuto –

+5

Gli IORef qui non sono idiomatici e di cattivo stile.Le variabili mutevoli non sono richieste per implementare l'attività che desidera. – nponeccop

5

Proprio come nel codice al di fuori di IO, è possibile stringa insieme una serie di calcoli utilizzando una piega. foldM opera all'interno di una monade, ad es.

main = do 
    conn <- connect "192.168.35.62" 8081 
    let tryOnePing (c, f) i = do 
     p <- ping conn "ping" 
     return $ if p == "pong" then (c+1, f) else (c, f+1) 
    (c, f) <- foldM tryOnePing (0, 0) [0 .. 10000] 
    print (c, f) 
+1

Direi che 'foldM' è al di là della comprensione dell'OP disponibile a questo punto – luqui

2

Le variabili sono immutabili in Haskell. Quando chiami inc f restituisce un valore di 0 + 1 che ignori prontamente. Il valore di f è 0 e rimarrà tale per sempre.

Problemi correlati