Haskell: Où vs Let

117

Je suis nouveau à Haskell et je suis très confus par Where vs. Let . Ils semblent tous deux avoir un objectif similaire. J'ai lu quelques comparaisons entre Where vs. Let mais j'ai du mal à discerner quand les utiliser. Quelqu'un pourrait-il fournir un contexte ou peut-être quelques exemples qui montrent quand utiliser l'un plutôt que l'autre?

Où vs laisser

Une whereclause ne peut être définie qu'au niveau d'une définition de fonction. Habituellement, cela correspond à la portée de la letdéfinition. La seule différence est lorsque des gardes sont utilisés . La portée de la whereclause s'étend à tous les gardiens. En revanche, la portée d'une letexpression est uniquement la clause de fonction actuelle et la garde, le cas échéant.

Aide-mémoire Haskell

Le Wiki Haskell est très détaillé et fournit divers cas mais il utilise des exemples hypothétiques. Je trouve ses explications trop brèves pour un débutant.

Avantages de Let :

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

Control.Monad.State

ne fonctionnera pas, car where se réfère au modèle correspondant à f =, où aucun x n'est dans la portée. En revanche, si vous aviez commencé avec let, vous n'auriez aucun problème.

Haskell Wiki sur les avantages de Let

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

Avantages de Où :

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)

Déclaration vs expression

Le wiki Haskell mentionne que la clause Where est déclarative tandis que l' expression Let est expressive. Mis à part le style, comment fonctionnent-ils différemment?

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. Dans le premier exemple, pourquoi le Let est-il dans la portée mais ne l'est pas?
  2. Est-il possible d'appliquer au premier exemple?
  3. Certains peuvent-ils appliquer cela à des exemples réels où les variables représentent des expressions réelles?
  4. Existe-t-il une règle générale à suivre pour utiliser chacun d'eux?

Mettre à jour

Pour ceux qui reviennent plus tard sur ce fil, j'ai trouvé la meilleure explication ici: " Une introduction douce à Haskell ".

Laissez Expressions.

Les expressions let de Haskell sont utiles chaque fois qu'un ensemble imbriqué de liaisons est requis. À titre d'exemple simple, considérons:

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

L'ensemble des liaisons créées par une expression let est mutuellement récursive et les liaisons de motifs sont traitées comme des motifs paresseux (c'est-à-dire qu'elles portent un ~ implicite). Les seuls types de déclarations autorisés sont les signatures de type, les liaisons de fonction et les liaisons de modèle.

Où les clauses.

Parfois, il est pratique de définir des liaisons sur plusieurs équations protégées, ce qui nécessite une clause where:

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

Notez que cela ne peut pas être fait avec une expression let, qui ne s'étend que sur l'expression qu'elle englobe. Une clause where n'est autorisée qu'au niveau supérieur d'un ensemble d'équations ou d'une expression de cas. Les mêmes propriétés et contraintes sur les liaisons dans les expressions let s'appliquent à celles des clauses where. Ces deux formes de portée imbriquée semblent très similaires, mais rappelez-vous qu'une expression let est une expression, alors qu'une clause where ne l'est pas - elle fait partie de la syntaxe des déclarations de fonction et des expressions case.

nbro
la source
9
J'ai été intrigué par la différence entre letet wherequand j'ai commencé à apprendre Haskell. Je pense que la meilleure façon de le comprendre est de se rendre compte qu'il y a très peu de différence entre les deux, et donc rien à craindre. Le sens du wheredonné en termes de letpar une transformation mécanique très simple. Voir haskell.org/onlinereport/decls.html#sect4.4.3.2 Cette transformation existe uniquement à des fins de notation, en réalité.
Tom Ellis
J'utilise généralement l'un ou l'autre en fonction de ce que je veux définir en premier. Par exemple, les gens utilisent souvent des fonctions, puis les définissent où. Let est utilisé si l'on veut une sorte de fonction de recherche impérative.
PyRulez
@Tom Ellis, Tom, j'essayais de comprendre le lien auquel vous faites référence mais c'était trop difficile pour moi, pourriez-vous expliquer cette simple transformation à de simples mortels?
jhegedus
1
@jhegedus: f = body where x = xbody; y = ybody ...signifief = let x = xbody; y = ybody ... in body
Tom Ellis
Merci Tom! Est-ce que ça peut aller dans l'autre sens? Est-il possible de transformer une expression let en une case .... of ... whereexpression d' une manière ou d' une autre? Je ne suis pas certain de cela.
jhegedus

Réponses:

39

1: Le problème dans l'exemple

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

est le paramètre x. Les éléments de la whereclause ne peuvent faire référence qu'aux paramètres de la fonction f(il n'y en a pas) et aux éléments des portées externes.

2: Pour utiliser a wheredans le premier exemple, vous pouvez introduire une deuxième fonction nommée qui prend le xcomme paramètre, comme ceci:

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

ou comme ça:

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

3: Voici un exemple complet sans les ...'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: Quand utiliser letou wherec'est une question de goût. J'utilise letpour accentuer un calcul (en le déplaçant vers l'avant) et wherepour souligner le déroulement du programme (en déplaçant le calcul vers l'arrière).

Antonakos
la source
2
"Les éléments de la clause where ne peuvent faire référence qu'aux paramètres de la fonction f (il n'y en a pas) et aux éléments des portées externes." - Cela m'aide vraiment à le clarifier.
28

Bien qu'il y ait la différence technique par rapport aux gardes que l'éphémère a souligné, il existe également une différence conceptuelle dans le fait de savoir si vous voulez mettre la formule principale au début avec des variables supplémentaires définies ci-dessous ( where) ou si vous voulez tout définir à l'avance et mettre la formule ci-dessous ( let). Chaque style a une emphase différente et vous voyez les deux utilisés dans les articles de mathématiques, les manuels, etc. En général, les variables qui sont suffisamment peu intuitives pour que la formule n'ait pas de sens sans elles doivent être définies ci-dessus; les variables intuitives en raison du contexte ou de leurs noms doivent être définies ci-dessous. Par exemple, dans l'exemple hasVowel d'Ephemient, la signification de vowelsest évidente et il n'est donc pas nécessaire de la définir au-dessus de son utilisation (sans tenir compte du fait que letcela ne fonctionnerait pas à cause de la garde).

gdj
la source
1
Cela fournit une bonne règle de base. Pourriez-vous expliquer pourquoi laissez semble avoir une portée différente de celle où?
Parce que la syntaxe Haskell le dit. Désolé, je n'ai pas de bonne réponse. Peut-être que les définitions de première portée sont difficiles à lire lorsqu'elles sont cachées sous un "let", donc elles ont été interdites.
gdj
13

Légal:

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

Non légal:

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

Légal:

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

Non légal: (contrairement à ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...
éphémère
la source
13
Pouvez-vous expliquer pourquoi les exemples suivants sont valables alors que les autres ne le sont pas?
2
Pour ceux qui ne le savent pas, c'est légal: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mest une nouvelle ligne)
Thomas Eding
5

J'ai trouvé cet exemple de LYHFGG utile:

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

letest une expression afin que vous puissiez mettre un let n'importe où (!) où les expressions peuvent aller.

En d'autres termes, dans l'exemple ci-dessus, il n'est pas possible d'utiliser wherepour simplement remplacer let(sans peut-être utiliser une caseexpression plus verbeuse combinée avec where).

jhegedus
la source
3

Malheureusement, la plupart des réponses ici sont trop techniques pour un débutant.

LHYFGG a un chapitre pertinent à ce sujet - que vous devriez lire si vous ne l'avez pas déjà fait, mais en substance:

  • whereest juste une construction syntaxique (pas un sucre ) qui n'est utile que pour les définitions de fonction .
  • let ... inest une expression elle - même , vous pouvez donc les utiliser partout où vous pouvez mettre une expression. Étant une expression en soi, il ne peut pas être utilisé pour lier des choses pour les gardes.

Enfin, vous pouvez également utiliser letdans les compréhensions de liste:

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

Nous incluons un let dans une compréhension de liste un peu comme nous le ferions pour un prédicat, mais il ne filtre pas la liste, il se lie uniquement aux noms. Les noms définis dans un let dans une compréhension de liste sont visibles par la fonction de sortie (la partie avant le |) et tous les prédicats et sections qui viennent après la liaison. Nous pourrions donc faire en sorte que notre fonction ne renvoie que les IMC des personnes> = 25:

Bora M. Alper
la source
C'est la seule réponse qui m'a vraiment aidé à comprendre la différence. Bien que les réponses techniques puissent être utiles pour un Haskell-er plus expérimenté, c'est assez succinct pour un débutant comme moi! +1
Zac G