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 è:
- invece di modificare qualcosa sul posto, restituisce un nuovo valore
- 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:
- E non passa i nostri due contatori tra iterazioni
_
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)
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 è? –
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 ?? –
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