sintaxis - sumatoria en haskell
En Haskell, ¿cuándo usamos adentro con let? (4)
Algunas notas para principiantes sobre "están siguiendo dos iguales".
Por ejemplo, add1
es una función que agrega 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 documentation :
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 where
realmente hacen y in
palabra clave.
En el siguiente código, la última frase que puedo poner al frente. ¿Cambiará algo?
Otra pregunta: si decido ponerme in
frente de la última frase, ¿debo sangrarla?
Intenté sin sangrar y abrazos quejas
El ú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, entonces la gente parece no entender lo que estoy diciendo. Permítanme 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 pregunta sobre el alcance de las consolidaciones declaradas en let
: Leí here que:
where
cláusulas.
A veces es conveniente realizar enlaces a varias ecuaciones protegidas, 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 abarca la expresión que encierra .
Mi pregunta: entonces, los dígitos variables no deberían estar visibles para la última frase impresa. ¿Extraño algo aquí?
En la notación do
, puedes usar let
con y sin in
. Para que sea equivalente (en su caso, mostraré más adelante un ejemplo en el que necesita agregar un segundo do
y por lo tanto más sangrado), debe aplicar una sangría como descubrió (si usa el diseño, si usa llaves y puntos y coma explícitos, son exactamente equivalentes).
Para entender por qué es equivalente, debes asimilar mónadas (al menos hasta cierto punto) y observar las reglas deslumbrantes para la notación do
. En particular, código como este:
do let x = ...
stmts -- the rest of the do block
se traduce para let x = ... in do { stmts }
. En su caso, stmts = print (problem_8 digits)
. Evaluar todo el desajuste let
resultados vinculantes en una acción IO (desde print $ ...
). Y aquí, necesita una comprensión de las mónadas para acordar intuitivamente que no hay diferencia entre las anotaciones de do
y los elementos de lenguaje "regulares" que describen un cálculo que da como resultado valores monádicos.
En cuanto a ambos, ¿por qué son posibles? Bueno, let ... in ...
tiene una amplia gama de aplicaciones (la mayoría de las cuales no tienen nada que ver con las mónadas en particular), y una larga historia para arrancar. por otra parte, let
sin para do
notación, parece ser nada más que una pequeña pieza de azúcar sintáctica. 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 $ ...
sin sentido 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 do
adicional para lo que sigue al let
es que solo tienes una línea. Recuerde, do e
e
.
En cuanto a tu edición: el digit
se ve en la siguiente línea es el punto completo. Y no hay excepción para eso ni nada. do
notación do
convierte en una sola expresión, y let
funcione bien en una sola expresión. where
solo se necesita para cosas que no son expresiones.
Por el bien de la demostración, mostraré la versión desacralizada de tu bloque do
. Si aún no está muy familiarizado con las mónadas (algo que debe cambiar pronto en mi humilde opinión), ignore el operador >>=
y concéntrese 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))
Primero, ¿por qué abrazos? La plataforma Haskell es generalmente el camino recomendado para los novatos, que viene con GHC.
Ahora, let
a la palabra clave let
. La forma más simple de esta palabra clave está destinada a ser siempre utilizada con.
let {assignments} in {expression}
Por ejemplo,
let two = 2; three = 3 in two * three
Las {assignments}
solo están en el alcance en la {expression}
correspondiente. Se aplican reglas de diseño regulares, lo que significa que debe estar sangrado al menos tanto como el let
que corresponda, y cualquier subexpresión que pertenezca a la expresión let
también debe sangrarse al menos tanto. Esto no es 100% cierto, pero es una buena regla general; Las reglas de diseño de Haskell son algo a lo que se acostumbrará con el tiempo a medida que lee y escribe el código 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 escribir: hacer notación y enumerar comprensiones (en realidad, comprensión de mónadas). El alcance de las asignaciones para estos casos prácticos está predefinido.
do foo
let {assignments}
bar
baz
Para la notación do
, las {assignments}
están en el alcance de 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
Enumere las comprensiones (o realmente, cualquier comprensión de mónadas) desugar en la notación do, para que proporcionen una facilidad similar.
[ baz | foo, let {assignments}, bar ]
Las {assignments}
están en el alcance de las expresiones bar
y baz
, pero no para foo
.
where
es algo diferente. Si no me equivoco, el alcance de where
alinea con una definición de función particular. Asi que
someFunc x y | guard1 = blah1
| guard2 = blah2
where {assignments}
las {assignments}
en esta cláusula where
tienen acceso a x
y y
. guard1
, guard2
, blah1
y blah2
tienen acceso a {assignments}
de esta cláusula where
. Como se menciona en el tutorial que ha vinculado, esto puede ser útil si varios guardias reutilizan las mismas expresiones.
Respuesta corta : use let
sin in
en el cuerpo de un do-block, y en la parte después de |
en una lista de comprensión. En cualquier otro lugar, use let ... in ...
La palabra clave let
se usa de tres maneras en Haskell.
La primera forma es una expresión let .
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 una declaración de let . Esta forma solo se usa dentro de la notación do, y no se usa
in
do statements let variable = expression statements
El tercero es similar al número 2 y se usa dentro de la lista de comprensiones. Nuevamente, no
in
.> [(x, y) | x <- [1..3], let y = 2*x] [(1,2),(2,4),(3,6)]
Esta forma vincula una variable que está en el alcance en generadores posteriores y en la expresión anterior a
|
.
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 ..
sea solo una expresión.
Debido a las reglas de indentación de haskell, una línea indentada más allá de 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, obtienes un error de análisis:
do (let x = 42 in)
foo
En conclusión, nunca lo use in
una lista de comprensión o un do-block. Es innecesario y confuso, ya que esos constructos ya tienen su propia forma de let
.