2011-11-21 19 views
7

Intento hacer un contador simple. Mis contadores no suben sin embargo. Me parece como si fueran reinicializados cada vez por la función "inc" o quizás el (n + 1) no funciona. ¿Cómo puedo arreglar esto?Los contadores se inicializan cada vez?

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) 

Respuesta

21

Aunque las variables mutables se pueden usar en Haskell como lo muestran otros comentaristas, no es un buen estilo: la mutación no se debe usar en la mayoría de los casos. La función inc acepta su argumento por valor, es decir, no modifica su argumento Además, las variables declaradas por let mantienen sus valores iniciales, por lo que no puede cambiarlos.

¿Cómo se escribe si no se puede cambiar ninguna variable? La respuesta es:

  1. en lugar de modificar algo en el lugar, devuelven un valor nuevo
  2. para los bucles, el uso de la recursividad

Afortunadamente, rara vez se necesita para escribir la recursividad a sí mismo, ya que la mayoría de recursiva los patrones ya están en la biblioteca estándar.

En su caso, debe realizar varias acciones de E/S y devolver el valor final de los dos contadores. Vamos a empezar desde una acción:

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

Aquí declaramos una función local con 2 parámetros: los valores actuales de los contadores, envasados ​​en una tupla (Int, Int) (una estructura en otros idiomas) y la iteración actual Int. La función realiza acciones IO y devuelve valores modificados de los contadores IO (Int, Int). Todo esto está indicado en su tipo:

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

ping devuelve un valor de IO String tipo. Para compararlo, necesita un String sin IO.Para ello, se debe utilizar >>= función:

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

Como este patrón es común, puede escribirse así

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

Pero el significado es exactamente el mismo (compilador traduce do notación en las aplicaciones de >>=).

El procesamiento muestra unos cuantos patrones comunes:

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

Aquí if no es un imperativo if pero más como un condition ? value1 : value2 operador ternario en otros idiomas. También tenga en cuenta que nuestra función tryOnePing acepta (c, f) y devuelve (c+1, f) o (c, f+1). Usamos tuplas ya que necesitamos trabajar solo con 2 contadores. En el caso de una gran cantidad de contadores, necesitaríamos declarar un tipo de estructura y usar campos con nombre.

El valor de todo el constructo If es una tupla (Int, Int). ping es una acción IO, por lo que tryOnePing también debe ser una acción IO. La función return no es un retorno imperativo sino una forma de convertir (Int, Int) en IO (Int, Int).

Así que, como tenemos tryOnePing, necesitamos escribir un bucle para ejecutarlo 1000 veces. Su forM_ no fue una buena elección:

  1. No pasa nuestros dos contadores entre iteraciones
  2. _ indica que arroja el valor final de los contadores de distancia en lugar de devolverlo

Usted necesita aquí no forM_ pero foldM

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

foldM realiza una acción IO parametrizada por cada elemento de la lista y pasa algún estado entre iteraciones, en nuestro caso los dos contadores. Acepta el estado inicial y devuelve el estado final. Por supuesto, como las acciones de sus realiza IO, devuelve IO (Int, Int), por lo que debemos utilizar >>= para extraer de nuevo para mostrar:

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

En Haskell, puede realizar la llamada 'reducciones de ETA , es decir, puede eliminar los mismos identificadores de ambos lados de una declaración de función. P.ej. \foo -> bar foo es lo mismo que bar. Así pues, en este caso con >>= puede escribir:

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

que es mucho más corta que la notación do:

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

También tenga en cuenta que usted no necesita tener dos contadores: si tiene éxito 3000 entonces tienes 7000 fallas. Por lo que el código se convierte en:

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) 

Por último, en Haskell su buena para separar acciones IO de código no-IO.Así que es mejor para recoger todos los resultados de los pings en una lista y luego contar los pings éxito en ella:

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) 

Tenga en cuenta que hay que evitar incrementar por completo.

Puede escribirse aún más corto, pero requiere más habilidad para leer y escribir. No se preocupe, pronto aprenderá estos trucos:

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

Gracias a mi" defensa "debo decir que sé sobre la inmutabilidad. Sin embargo, encontré el" n + 1 "aquí: http://hackage.haskell.org/packages/archive/mtl/1.1.0.2/doc/html/Control-Monad-State-Lazy.html y aquí http://www.haskell.org/tutorial /goodies.html y me preguntaba si podría funcionar. Desde que compilé, creo ht, - wooow, esto funciona. ¿Estos tics apuntan a resolver el mismo problema, los contadores que son? –

+0

Gracias. Por cierto: al sincronizar el fragmento de la respuesta # 1 con IORefs obtengo la mitad del tiempo de ejecución en comparación con el tiempo 'pings <- time (mapM tryOnePing [0 .. 1000000])'. Supongo que en el primer caso no espera el tiempo de espera para las respuestas en el tiempo. ¿Podría tener eso que ver con la naturaleza perezosa o podría ser que este constructo de IORef se ejecute más rápido? –

+0

Podría ser ambos. Utilice perfiles y/o consulte el lenguaje core haskell de nivel inferior para ver. También use -O2 ya que mi código más corto depende en gran medida de la presencia de optimizaciones. – nponeccop

7

En Haskell los datos son inmutables por defecto. Esto significa que el c en inc c siempre es cero.

Para obtener variables mutables en Haskell, tiene que pedirlas explícitamente, es decir, usando IORefs. El uso de ellos podría escribir algo como:

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

Gracias! Eso funciona. Leeré sobre esto. Un problema más con el código es que informa un tiempo de ejecución de p. Ej. 2 segundos, pero corre 15 segundos hasta que regrese a la consola :-(. Pero gracias por su solución, es una gran ayuda. –

+5

IORefs aquí no son idiomáticos y un mal estilo. No se requieren variables mutables para implementar la tarea que quiere. – nponeccop

5

Al igual que en el código fuera de IO, puede encadenar una serie de cálculos utilizando un pliegue. foldM opera dentro de una mónada, p.

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

Supongo que 'foldM' está más allá de la comprensión disponible del OP en este punto. – luqui

2

Las variables son inmutables en Haskell. Cuando llama al inc f, devuelve un valor de 0 + 1 que inmediatamente ignora. El valor de f es 0 y lo seguirá siendo todo el tiempo.

Cuestiones relacionadas