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í?
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.
-
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
-
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
-
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
.
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.
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))
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.
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.
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