En Haskell, ¿cuándo usamos in con let?


En el siguiente código, la última frase puedo poner un in delante. ¿Cambiará algo?

Otra pregunta: Si decido poner in delante de la última frase, ¿necesito sangrarla?

Lo intenté sin sangrar y abrazos se queja

Último generador en do {...} debe ser una expresión

import Data.Char
groupsOf _ [] = []
groupsOf n xs = 
    take n xs : groupsOf n ( tail xs )

problem_8 x = maximum . map product . groupsOf 5 $ x
main = do t <- readFile "p8.log" 
          let digits = map digitToInt $concat $ lines t
          print $ problem_8 digits

Editar

Ok, así que la gente no parece entender lo que estoy diciendo. Déjame reformular: son los dos siguientes lo mismo, dado ¿el contexto anterior?

1.

let digits = map digitToInt $concat $ lines t
print $ problem_8 digits

2.

let digits = map digitToInt $concat $ lines t
in print $ problem_8 digits

Otra cuestión relativa al alcance de las consolidaciones declaradas en let: Leo aquí que:

where Cláusulas.

A veces es conveniente aplicar enlaces sobre varias ecuaciones guardadas, lo que requiere una cláusula where:

f x y  |  y>z           =  ...
       |  y==z          =  ...
       |  y<z           =  ...
     where z = x*x

Tenga en cuenta que esto no se puede hacer con una expresión let, que solo se extiende sobre la expresión que encierra.

Mi pregunta: por lo tanto, los dígitos variables no deben ser visibles hasta la última frase de impresión. ¿Me pierdo algo aquí?

Author: nbro, 2011-11-26

4 answers

Respuesta corta : Use let sin in en el cuerpo de un do-block, y en la parte después de | en una comprensión de lista. En cualquier otro lugar, utilice let ... in ....


La palabra clave let se usa de tres maneras en Haskell.

  1. La primera forma es una let-expression.

    let variable = expression in expression
    

    Esto se puede usar siempre que se permita una expresión, por ejemplo,

    > (let x = 2 in x*2) + 3
    7
    
  2. El segundo es un let-statement. Este formulario solo se utiliza dentro de do-notación, y no utiliza in.

    do statements
       let variable = expression
       statements
    
  3. El tercero es similar al número 2 y se usa dentro de las comprensiones de listas. De nuevo, no in.

    > [(x, y) | x <- [1..3], let y = 2*x]
    [(1,2),(2,4),(3,6)]
    

    Esta forma enlaza una variable que está en alcance en generadores posteriores y en la expresión antes del |.


La razón de su confusión aquí es que las expresiones (del tipo correcto) se pueden usar como declaraciones dentro de un do-block, y let .. in .. es solo un expresion.

Debido a las reglas de sangría de haskell, una línea sangrada más que la anterior significa que es una continuación de la línea anterior, por lo que este

do let x = 42 in
     foo

Se analiza como

do (let x = 42 in foo)

Sin sangría, se obtiene un error de análisis:

do (let x = 42 in)
   foo

En conclusión, nunca use in en una comprensión de lista o en un do-block. Es innecesario y confuso, ya que esas construcciones ya tienen su propia forma de let.

 98
Author: hammar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-25 22:41:15

En primer lugar, ¿por qué abrazos? La plataforma Haskell es generalmente la forma recomendada para los novatos, que viene con GHC.

Ahora, vamos a la palabra clave let. La forma más simple de esta palabra clave está destinada a siempre ser utilizado con in.

let {assignments} in {expression}

Por ejemplo,

let two = 2; three = 3 in two * three

Los {assignments} son solo en el ámbito de aplicación de los {expression} correspondientes. Se aplican reglas de diseño regulares, lo que significa que in debe sangrarse al menos tanto como el let que corresponde a, y cualquier sub-expresión perteneciente a la expresión let debe igualmente sangrar al menos tanto. Esto en realidad no es 100% cierto, pero es una buena regla general; las reglas de diseño de Haskell son algo a lo que te acostumbrarás con el tiempo mientras lees y escribes código de Haskell. Solo tenga en cuenta que la cantidad de sangría es la forma principal de indicar qué código pertenece a qué expresión.

Haskell proporciona dos casos de conveniencia en los que no tiene que write in: do notation and list comprehensions (actually, monad comprehensions). El alcance de las asignaciones para estos casos de conveniencia está predefinido.

do foo
   let {assignments}
   bar
   baz

Para la notación do, los {assignments} están en el alcance para cualquier declaración que siga, en este caso, bar y baz, pero no foo. Es como si hubiéramos escrito

do foo
   let {assignments}
   in do bar
         baz

Listar comprensiones (o realmente, cualquier comprensión de mónada) desugar en notación do, por lo que proporcionan una facilidad similar.

[ baz | foo, let {assignments}, bar ]

El {assignments} están en el ámbito de las expresiones bar y baz, pero no para foo.


where es algo diferente. Si no me equivoco, el alcance de where se alinea con una definición de función particular. So

someFunc x y | guard1 = blah1
             | guard2 = blah2
  where {assignments}

El {assignments} en esta cláusula where tienen acceso a x y y. guard1, guard2, blah1, y blah2 todos tienen acceso a la {assignments} este where cláusula. Como se menciona en el tutorial que vinculó, esto puede ser útil si varios guardias reutilizan el mismo expresiones.

 17
Author: Dan Burton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-25 23:00:03

En notación do, se puede usar let con y sin in. Para que sea equivalente (en su caso, más adelante mostraré un ejemplo donde necesita agregar un segundo do y por lo tanto más sangría), necesita sangrar como descubrió (si está usando layout - si usa llaves explícitas y punto y coma, son exactamente equivalentes).

Para entender por qué es equivalente, tienes que realmente grok mónadas (al menos hasta cierto punto) y mirar las reglas de desugaring para la notación do. En particular, código como este:

do let x = ...
   stmts -- the rest of the do block

Se traduce a let x = ... in do { stmts }. En su caso, stmts = print (problem_8 digits). Evaluar el enlace completo desugared let resulta en una acción IO (de print $ ...). Y aquí, necesitas entender las mónadas para acordar intuitivamente que no hay diferencia entre las notaciones do y los elementos del lenguaje "regular" que describen un cálculo que resulta en valores monádicos.

En cuanto a ambos por qué son posibles: Bueno, let ... in ... tiene una amplia gama de aplicaciones (la mayoría de los cuales no tienen nada que ver con mónadas en particular), y una larga historia para arrancar. let sin in para do notación, por otro lado, parece ser nada más que un pequeño pedazo de azúcar sintáctico. La ventaja es obvia: Puede vincular los resultados de cálculos puros (como en, no monádicos) a un nombre sin recurrir a un val <- return $ ... inútil y sin dividir el bloque do en dos:

do stuff
   let val = ...
    in do more
          stuff $ using val

La razón por la que no necesitas un bloque adicional do para lo que sigue al let es que sólo tengo una sola línea. Recuerda, do e es e.

Con respecto a su edición: digit ser visible en la siguiente línea es todo el punto. Y no hay excepción para eso ni nada. do la notación se convierte en una sola expresión, y let funciona bien en una sola expresión. where solo es necesario para cosas que no son expresiones.

Por el bien de la demostración, voy a mostrar la versión desugared de su bloque do. Si aún no estás muy familiarizado con las mónadas (algo que debe cambiar pronto en mi humilde opinión), ignorar el operador >>= y centrarse en el let. También tenga en cuenta que la sangría ya no importa.

main = readFile "p8.log" >>= (\t ->
  let digits = map digitToInt $ concat $ lines t
  in print (problem_8 digits))
 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-25 22:41:19

Algunas notas para principiantes sobre "están siguiendo a dos lo mismo".

Por ejemplo, add1 es una función, que suma 1 al número:

add1 :: Int -> Int
add1 x =
    let inc = 1
    in x + inc

Por lo tanto, es como add1 x = x + inc con la sustitución inc por 1 de la palabra clave let.

Cuando intentas suprimir in palabra clave

add1 :: Int -> Int
add1 x =
    let inc = 1
    x + inc

Tienes un error de análisis.

De la documentación :

Within do-blocks or list comprehensions 
let { d1 ; ... ; dn } 
without `in` serves to introduce local bindings. 

Por cierto, hay una buena explicación con muchos ejemplos sobre lo que las palabras clave where y in realmente hacen.

 1
Author: ДМИТРИЙ МАЛИКОВ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-25 22:37:05