Haskell: Onde vs. Let

117

Eu sou novo em Haskell e estou muito confuso com Where vs. Let . Ambos parecem fornecer um propósito semelhante. Eu li algumas comparações entre Where vs. Let, mas estou tendo problemas para discernir quando usar cada uma. Alguém poderia fornecer algum contexto ou talvez alguns exemplos que demonstrem quando usar um em vez do outro?

Onde vs. Let

Uma wherecláusula só pode ser definida no nível da definição de uma função. Normalmente, isso é idêntico ao escopo da letdefinição. A única diferença é quando os guardas estão sendo usados . O escopo da wherecláusula se estende a todos os guardas. Em contraste, o escopo de uma letexpressão é apenas a cláusula de função atual e guarda, se houver.

Folha de referências de Haskell

The Haskell Wiki é muito detalhado e fornece vários casos, mas usa exemplos hipotéticos. Acho suas explicações muito breves para um iniciante.

Vantagens do Let :

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

Control.Monad.State

não funcionará, porque onde se refere ao padrão correspondente f =, onde nenhum x está no escopo. Em contraste, se você tivesse começado com let, não teria problemas.

Haskell Wiki sobre as vantagens do Let

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

Vantagens de onde :

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)

Declaração vs. Expressão

O wiki Haskell menciona que a cláusula Where é declarativa, enquanto a expressão Let é expressiva. Além do estilo, como eles atuam de forma 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. No primeiro exemplo, por que o escopo Let está dentro, mas onde não está?
  2. É possível aplicar Onde ao primeiro exemplo?
  3. Alguns podem aplicar isso a exemplos reais onde as variáveis ​​representam expressões reais?
  4. Existe uma regra geral a ser seguida quando usar cada um?

Atualizar

Para aqueles que vêm por este tópico mais tarde, encontrei a melhor explicação a ser encontrada aqui: " Uma introdução gentil a Haskell ".

Let Expressions.

As expressões let de Haskell são úteis sempre que um conjunto aninhado de ligações é necessário. Como um exemplo simples, considere:

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

O conjunto de associações criadas por uma expressão let é mutuamente recursiva e as associações de padrão são tratadas como padrões preguiçosos (ou seja, carregam um ~ implícito). Os únicos tipos de declarações permitidas são assinaturas de tipo, associações de função e associações de padrão.

Onde Cláusulas.

Às vezes, é conveniente definir o escopo das ligações em várias equações protegidas, o que requer uma cláusula where:

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

Observe que isso não pode ser feito com uma expressão let, que abrange apenas a expressão que ela inclui. Uma cláusula where só é permitida no nível superior de um conjunto de equações ou expressão case. As mesmas propriedades e restrições sobre ligações em expressões let se aplicam àquelas em cláusulas where. Essas duas formas de escopo aninhado parecem muito semelhantes, mas lembre-se de que uma expressão let é uma expressão, enquanto uma cláusula where não é - ela é parte da sintaxe de declarações de função e expressões case.

Nbro
fonte
9
Fiquei intrigado com a diferença entre lete wherequando comecei a aprender Haskell. Acho que a melhor maneira de entender isso é perceber que há muito pouca diferença entre os dois e, portanto, nada com que se preocupar. O significado de wheredado em termos de letuma transformação mecânica muito simples. Veja haskell.org/onlinereport/decls.html#sect4.4.3.2 Essa transformação existe apenas para conveniência de notação, na verdade.
Tom Ellis
Normalmente, uso um ou outro, dependendo do que desejo definir primeiro. Por exemplo, as pessoas costumam usar funções e, em seguida, defini-las onde. Let é usado se alguém deseja uma função de aparência imperativa.
PyRulez
@Tom Ellis, Tom, estava tentando entender o link ao qual você se refere, mas era muito difícil para mim, você poderia explicar essa simples transformação para meros mortais?
jhegedus
1
@jhegedus: f = body where x = xbody; y = ybody ...significaf = let x = xbody; y = ybody ... in body
Tom Ellis
Obrigado Tom! Pode acontecer o contrário? É possível transformar uma expressão let em uma case .... of ... whereexpressão de alguma forma? Eu não tenho certeza sobre isso.
jhegedus

Respostas:

39

1: O problema no exemplo

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

é o parâmetro x. Coisas na wherecláusula podem se referir apenas aos parâmetros da função f(não há nenhum) e coisas em escopos externos.

2: Para usar a whereno primeiro exemplo, você pode introduzir uma segunda função nomeada que leva o xcomo um parâmetro, como este:

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

ou assim:

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

3: Aqui está um exemplo completo sem o ...'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: Quando usar letou whereé uma questão de gosto. Eu uso letpara enfatizar um cálculo (movendo-o para a frente) e wherepara enfatizar o fluxo do programa (movendo o cálculo para trás).

antonakos
fonte
2
"Coisas na cláusula where podem se referir apenas aos parâmetros da função f (não há nenhum) e coisas em escopos externos." - Isso realmente ajuda a esclarecer para mim.
28

Embora haja uma diferença técnica em relação aos guardas que aquele efemiente apontou, também há uma diferença conceitual se você deseja colocar a fórmula principal antecipadamente com as variáveis ​​extras definidas abaixo ( where) ou se você deseja definir tudo antecipadamente e colocar a fórmula abaixo ( let). Cada estilo tem uma ênfase diferente e você vê ambos usados ​​em trabalhos de matemática, livros didáticos, etc. Geralmente, variáveis ​​que são suficientemente não intuitivas para que a fórmula não faça sentido sem elas devem ser definidas acima; variáveis ​​que são intuitivas devido ao contexto ou seus nomes devem ser definidas a seguir. Por exemplo, no exemplo hasVowel do ephemient, o significado de vowelsé óbvio e por isso não precisa ser definido acima de seu uso (desconsiderando o fato de que letnão funcionaria devido ao guarda).

gdj
fonte
1
Isso fornece uma boa regra prática. Você poderia explicar por que vamos ter um escopo diferente de onde?
Porque a sintaxe Haskell diz isso. Desculpe, não tenho uma boa resposta. Talvez as definições de escopo superior sejam difíceis de ler quando colocadas sob um "let", portanto, não foi permitido.
gdj
13

Legal:

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

Não é legal:

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

Legal:

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

Não legal: (ao contrário de ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...
efêmero
fonte
13
Você pode explicar por que os exemplos a seguir são válidos enquanto os outros não?
2
Para quem não sabe, isso é legal: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mé nova linha)
Thomas Eding
5

Achei este exemplo do LYHFGG útil:

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

leté uma expressão para que você possa colocar um em let qualquer lugar (!) onde as expressões possam ir.

Em outras palavras, no exemplo acima, não é possível usar wherepara simplesmente substituir let(sem talvez usar alguma caseexpressão mais detalhada combinada com where).

Jhegedus
fonte
3

Infelizmente, a maioria das respostas aqui é muito técnica para um iniciante.

LHYFGG tem um capítulo relevante sobre isso - que você deve ler se ainda não o fez, mas em essência:

  • whereé apenas uma construção sintática (não um açúcar ) que é útil apenas em definições de funções .
  • let ... iné uma expressão em si , portanto, você pode usá-los onde quer que coloque uma expressão. Sendo uma expressão em si, não pode ser usado para amarrar coisas para guardas.

Por último, você também pode usar letem compreensões de lista:

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

Incluímos um let dentro de uma compreensão de lista da mesma forma que faríamos com um predicado, só que não filtra a lista, apenas vincula a nomes. Os nomes definidos em um let dentro de uma compreensão de lista são visíveis para a função de saída (a parte antes de |) e todos os predicados e seções que vêm depois da vinculação. Assim, poderíamos fazer nossa função retornar apenas o IMC de pessoas> = 25:

Bora M. Alper
fonte
Esta é a única resposta que realmente me ajudou a entender a diferença. Embora as respostas técnicas possam ser úteis para um Haskell-er mais experiente, elas são sucintas o suficiente para um iniciante como eu! +1
Zac G