Haskell: ¿Dónde vs Dejar?

117

Soy nuevo en Haskell y estoy muy confundido con Where vs. Let . Ambos parecen tener un propósito similar. He leído algunas comparaciones entre Dónde y Dejar, pero tengo problemas para discernir cuándo usar cada una. ¿Podría alguien proporcionar algún contexto o quizás algunos ejemplos que demuestren cuándo usar uno sobre el otro?

Donde vs Dejar

Una wherecláusula solo se puede definir a nivel de definición de función. Por lo general, eso es idéntico al alcance de la letdefinición. La única diferencia es cuando se utilizan protectores . El alcance de la wherecláusula se extiende a todos los guardias. Por el contrario, el alcance de una letexpresión es solo la cláusula de función actual y la protección, si corresponde.

Hoja de trucos de Haskell

La Wiki de Haskell es muy detallada y proporciona varios casos, pero utiliza ejemplos hipotéticos. Encuentro sus explicaciones demasiado breves para un principiante.

Ventajas de Let :

f :: State s a
f = State $ \x -> y
   where y = ... x ...

Control.Monad.State

no funcionará, porque where se refiere al patrón que coincide con f =, donde no hay x en el alcance. Por el contrario, si hubiera comenzado con let, no tendría problemas.

Wiki de Haskell sobre las ventajas de Let

f :: State s a
f = State $ \x ->
   let y = ... x ...
   in  y

Ventajas de Dónde :

f x
  | cond1 x   = a
  | cond2 x   = g a
  | otherwise = f (h x a)
  where
    a = w x

f x
  = let a = w x
    in case () of
        _ | cond1 x   = a
          | cond2 x   = g a
          | otherwise = f (h x a)

Declaración frente a expresión

La wiki de Haskell menciona que la cláusula Where es declarativa mientras que la expresión Let es expresiva. Aparte del estilo, ¿cómo funcionan de manera diferente?

Declaration style                     | Expression-style
--------------------------------------+---------------------------------------------
where clause                          | let expression
arguments LHS:     f x = x*x          | Lambda abstraction: f = \x -> x*x
Pattern matching:  f [] = 0           | case expression:    f xs = case xs of [] -> 0
Guards:            f [x] | x>0 = 'a'  | if expression:      f [x] = if x>0 then 'a' else ...
  1. En el primer ejemplo, ¿por qué está el alcance Let in pero dónde no?
  2. ¿Es posible aplicar Where al primer ejemplo?
  3. ¿Pueden algunos aplicar esto a ejemplos reales donde las variables representan expresiones reales?
  4. ¿Existe una regla general a seguir cuando usar cada uno?

Actualizar

Para aquellos que vienen por este hilo más adelante, encontré la mejor explicación que se puede encontrar aquí: " Una suave introducción a Haskell ".

Dejemos Expressions.

Las expresiones let de Haskell son útiles siempre que se requiera un conjunto anidado de enlaces. Como ejemplo simple, considere:

let y   = a*b
    f x = (x+y)/y
in f c + f d

El conjunto de enlaces creados por una expresión let es recursivo entre sí, y los enlaces de patrones se tratan como patrones perezosos (es decir, llevan un ~ implícito). El único tipo de declaraciones permitidas son las firmas de tipos, los enlaces de funciones y los enlaces de patrones.

Dónde Cláusulas.

A veces es conveniente establecer el alcance de los enlaces sobre 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 se aplica a la expresión que encierra. Una cláusula where solo se permite en el nivel superior de un conjunto de ecuaciones o expresión de caso. Las mismas propiedades y restricciones sobre los enlaces en las expresiones let se aplican a las de las cláusulas where. Estas dos formas de ámbito anidado parecen muy similares, pero recuerde que una expresión let es una expresión, mientras que una cláusula where no lo es; es parte de la sintaxis de las declaraciones de funciones y las expresiones de casos.

nbro
fuente
9
Me desconcertó la diferencia entre lety wherecuando empecé a aprender Haskell. Creo que la mejor manera de entenderlo es darse cuenta de que hay muy poca diferencia entre los dos y, por lo tanto, no hay nada de qué preocuparse. El significado de wheredado en términos de a lettravés de una transformación mecánica muy simple. Ver haskell.org/onlinereport/decls.html#sect4.4.3.2 Esta transformación existe solo por conveniencia de notación, en realidad.
Tom Ellis
Normalmente uso uno u otro dependiendo de lo que quiero definir primero. Por ejemplo, las personas a menudo usan funciones y luego las definen en dónde. Let se usa si se quiere una especie de función de aspecto imperativo.
PyRulez
@Tom Ellis, Tom, estaba tratando de entender el enlace al que te refieres, pero fue demasiado difícil para mí, ¿podrías explicar esta simple transformación a simples mortales?
jhegedus
1
@jhegedus: f = body where x = xbody; y = ybody ...significaf = let x = xbody; y = ybody ... in body
Tom Ellis
¡Gracias Tom! ¿Puede ir al revés? ¿Es posible transformar una expresión let en una case .... of ... whereexpresión de alguna manera? No estoy seguro de esto.
jhegedus

Respuestas:

39

1: El problema del ejemplo

f :: State s a
f = State $ \x -> y
    where y = ... x ...

es el parámetro x. Las cosas en la wherecláusula pueden referirse solo a los parámetros de la función f(no hay ninguna) y las cosas en ámbitos externos.

2: Para usar a whereen el primer ejemplo, puede introducir una segunda función nombrada que toma xcomo parámetro, así:

f = State f'
f' x = y
    where y = ... x ...

o así:

f = State f'
    where
    f' x = y
        where y = ... x ...

3: Aquí hay un ejemplo completo sin los ...'s:

module StateExample where

data State a s = State (s -> (a, s))

f1 :: State Int (Int, Int)
f1 = State $ \state@(a, b) ->
    let
        hypot = a^2 + b^2
        result = (hypot, state)
    in result

f2 :: State Int (Int, Int)
f2 = State f
    where
    f state@(a, b) = result
        where
        hypot = a^2 + b^2
        result = (hypot, state)

4: Cuándo usar leto wherees cuestión de gustos. Utilizo letpara enfatizar un cálculo (moviéndolo hacia el frente) y wherepara enfatizar el flujo del programa (moviendo el cálculo hacia atrás).

Antonakos
fuente
2
"Las cosas en la cláusula where pueden referirse solo a los parámetros de la función f (no hay ninguna) y las cosas en ámbitos externos". - Eso realmente me ayuda a aclararlo.
28

Si bien existe la diferencia técnica con respecto a los guardias que señaló ephemient, también hay una diferencia conceptual en si desea poner la fórmula principal al principio con variables adicionales definidas a continuación ( where) o si desea definir todo por adelantado y poner la fórmula debajo ( let). Cada estilo tiene un énfasis diferente y se ven ambos usados ​​en trabajos de matemáticas, libros de texto, etc. Generalmente, las variables que son lo suficientemente poco intuitivas como para que la fórmula no tenga sentido sin ellas deben definirse arriba; las variables que son intuitivas debido al contexto o sus nombres deben definirse a continuación. Por ejemplo, en el ejemplo de hasVowel de ephemient, el significado de vowelses obvio y, por lo tanto, no es necesario definirlo por encima de su uso (sin tener en cuenta el hecho de que letno funcionaría debido a la guardia).

gdj
fuente
1
Esto proporciona una buena regla general. ¿Podrías explicar por qué let parece tener un alcance diferente al de dónde?
Porque la sintaxis de Haskell lo dice. Lo siento, no tengo una buena respuesta. Quizás las definiciones de alto alcance son difíciles de leer cuando se colocan debajo de un "permitir", por lo que no se permitieron.
gdj
13

Legal:

main = print (1 + (let i = 10 in 2 * i + 1))

Ilegal:

main = print (1 + (2 * i + 1 where i = 10))

Legal:

hasVowel [] = False
hasVowel (x:xs)
  | x `elem` vowels = True
  | otherwise = False
  where vowels = "AEIOUaeiou"

No legal: (a diferencia de ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...
efímero
fuente
13
¿Puede explicar por qué los siguientes ejemplos son válidos y el otro no lo es?
2
Para aquellos que no lo saben, esto es legal: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mes nueva línea)
Thomas Eding
5

Encontré este ejemplo de LYHFGG útil:

ghci> 4 * (let a = 9 in a + 1) + 2  
42  

letes una expresión para que pueda poner un let lugar (!) donde pueden ir las expresiones.

En otras palabras, en el ejemplo anterior es no posible utilizar wheresimplemente para reemplazar let(sin tal vez usando un poco más detallado caseexpresión combinada con where).

jhegedus
fuente
3

Lamentablemente, la mayoría de las respuestas aquí son demasiado técnicas para un principiante.

LHYFGG tiene un capítulo relevante al respecto, que debe leer si aún no lo ha hecho, pero en esencia:

  • wherees solo una construcción sintáctica (no un azúcar ) que son útiles solo en las definiciones de funciones .
  • let ... ines una expresión en sí misma , por lo que puede usarlos siempre que pueda poner una expresión. Siendo una expresión en sí misma, no puede usarse para atar cosas para los guardias.

Por último, también puede usar leten listas por comprensión:

calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
-- w: width
-- h: height

Incluimos un let dentro de la comprensión de una lista de manera muy similar a como lo haríamos con un predicado, solo que no filtra la lista, solo se une a los nombres. Los nombres definidos en un let dentro de una lista de comprensión son visibles para la función de salida (la parte antes de |) y todos los predicados y secciones que vienen después del enlace. Entonces podríamos hacer que nuestra función devuelva solo los IMC de personas> = 25:

Bora M. Alper
fuente
Esta es la única respuesta que realmente me ayudó a comprender la diferencia. Si bien las respuestas técnicas pueden ser útiles para un Haskell-er más experimentado, ¡esto es lo suficientemente sucinto para un principiante como yo! +1
Zac G