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:
- en lugar de modificar algo en el lugar, devuelven un valor nuevo
- 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:
- No pasa nuestros dos contadores entre iteraciones
_
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)
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? –
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? –
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