web-dev-qa-db-fra.com

À Haskell, quand utilisons-nous avec let?

Dans le code suivant, la dernière phrase que je peux mettre un in devant. Cela changera-t-il quelque chose?

Une autre question: si je décide de mettre in devant la dernière phrase, dois-je l'indenter?

J'ai essayé sans indentation et les câlins se plaignent

Le dernier générateur de do {...} doit être une expression

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

Modifier

Ok, donc les gens ne semblent pas comprendre ce que je dis. Permettez-moi de reformuler: les deux suivants sont-ils les mêmes, compte tenu du contexte ci-dessus?

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

Une autre question concernant la portée des liaisons déclarées dans let: J'ai lu ici que:

where Clauses.

Parfois, il est pratique d'étendre les 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 couvre que l'expression qu'elle contient .

Ma question: donc, les chiffres variables ne devraient pas être visibles pour la dernière phrase imprimée. Dois-je manquer quelque chose ici?

55
McBear Holden

Réponse courte: Utilisez let sans in dans le corps d'un do-block et dans la partie après le | dans une liste de compréhension. Partout ailleurs, utilisez let ... in ....


Le mot clé let est utilisé de trois façons dans Haskell.

  1. La première forme est une let-expression .

    let variable = expression in expression
    

    Cela peut être utilisé partout où une expression est autorisée, par ex.

    > (let x = 2 in x*2) + 3
    7
    
  2. Le second est une instruction let . Ce formulaire n'est utilisé qu'à l'intérieur de la do-notation et n'utilise pas in.

    do statements
       let variable = expression
       statements
    
  3. Le troisième est similaire au numéro 2 et est utilisé dans les listes de compréhension. Encore une fois, pas de in.

    > [(x, y) | x <- [1..3], let y = 2*x]
    [(1,2),(2,4),(3,6)]
    

    Ce formulaire lie une variable qui est dans la portée des générateurs suivants et dans l'expression avant le |.


La raison de votre confusion ici est que les expressions (du type correct) peuvent être utilisées comme des instructions dans un do-block, et let .. in .. n'est qu'une expression.

En raison des règles d'indentation de haskell, une ligne plus en retrait que la précédente signifie que c'est une continuation de la ligne précédente, donc cela

do let x = 42 in
     foo

obtient analysé comme

do (let x = 42 in foo)

Sans indentation, vous obtenez une erreur d'analyse:

do (let x = 42 in)
   foo

En conclusion, n'utilisez jamais in dans une compréhension de liste ou un do-block. C'est inutile et déroutant, car ces constructions ont déjà leur propre forme de let.

112
hammar

Tout d'abord, pourquoi des câlins? La Haskell Platform est généralement la voie à suivre recommandée pour les débutants, qui est fournie avec GHC.

Maintenant, passons au mot clé let. La forme la plus simple de ce mot-clé est destinée à toujours être utilisée avec in.

let {assignments} in {expression}

Par exemple,

let two = 2; three = 3 in two * three

Le {assignments} sont seulement dans la portée dans le {expression}. Des règles de mise en page régulières s'appliquent, ce qui signifie que in doit être mis en retrait au moins autant que le let auquel il correspond, et toutes les sous-expressions appartenant à l'expression let doit également être mis en retrait au moins autant. Ce n'est pas vrai à 100%, mais c'est une bonne règle de base; Les règles de mise en page Haskell sont quelque chose que vous allez simplement vous habituer au fil du temps que vous lisez et écrivez le code Haskell. Gardez juste à l'esprit que la quantité d'indentation est le principal moyen d'indiquer quel code appartient à quelle expression.

Haskell fournit deux cas pratiques où vous ne faites pas devez écrire in: faites des notations et listez les compréhensions (en fait, les compréhensions monades). La portée des affectations pour ces cas pratiques est prédéfinie.

do foo
   let {assignments}
   bar
   baz

Pour la notation do, le {assignments} sont à la portée de toutes les instructions qui suivent, dans ce cas, bar et baz, mais pas foo. C'est comme si nous avions écrit

do foo
   let {assignments}
   in do bar
         baz

Répertoriez les compréhensions (ou vraiment, toute compréhension de monade) en notation do, afin qu'elles fournissent une facilité similaire.

[ baz | foo, let {assignments}, bar ]

Le {assignments} sont à la portée des expressions bar et baz, mais pas pour foo.


where est quelque peu différent. Si je ne me trompe pas, la portée de where correspond à une définition de fonction particulière. Alors

someFunc x y | guard1 = blah1
             | guard2 = blah2
  where {assignments}

le {assignments} dans cette clause where a accès à x et y. guard1, guard2, blah1, et blah2 tous ont accès à {assignments} de cette clause where. Comme mentionné dans le didacticiel que vous avez lié, cela peut être utile si plusieurs gardes réutilisent les mêmes expressions.

18
Dan Burton

En notation do, vous pouvez en effet utiliser let avec et sans in. Pour qu'il soit équivalent (dans votre cas, je montrerai plus tard un exemple où vous devez ajouter un deuxième do et donc plus d'indentation), vous devez l'indenter comme vous l'avez découvert (si vous utilisez layout - si vous utilisez des accolades et des points-virgules explicites, ils sont exactement équivalents).

Pour comprendre pourquoi c'est équivalent, vous devez en fait grogner des monades (au moins dans une certaine mesure) et regarder les règles de désucréation pour la notation do. En particulier, un code comme celui-ci:

do let x = ...
   stmts -- the rest of the do block

est traduit en let x = ... in do { stmts }. Dans votre cas, stmts = print (problem_8 digits). L'évaluation de l'ensemble de la liaison désugarée let se traduit par une action IO (from print $ ...). Et ici, vous devez comprendre les monades pour convenir intuitivement qu'il n'y a pas de différence entre do notations et éléments de langage "réguliers" décrivant un calcul aboutissant à des valeurs monadiques.

Quant aux deux, pourquoi sont possibles: Eh bien, let ... in ... A un large éventail d'applications (dont la plupart n'ont rien à voir avec les monades en particulier), et une longue histoire à démarrer. let sans in pour la notation do, d'autre part, ne semble être qu'un petit morceau de sucre syntaxique. L'avantage est évident: vous pouvez lier les résultats de calculs purs (comme dans, pas monadiques) à un nom sans recourir à un val <- return $ ... Inutile et sans diviser le bloc do en deux:

do stuff
   let val = ...
    in do more
          stuff $ using val

La raison pour laquelle vous n'avez pas besoin d'un bloc do supplémentaire pour ce qui suit le let est que vous n'avez obtenu qu'une seule ligne. N'oubliez pas que do e Est e.

En ce qui concerne votre édition: digit être visible dans la ligne suivante est le point entier. Et il n'y a aucune exception pour cela ou quoi que ce soit. do la notation devient une seule expression, et let fonctionne très bien dans une seule expression. where n'est nécessaire que pour les choses qui ne sont pas des expressions.

À des fins de démonstration, je vais vous montrer la version désucrée de votre bloc do. Si vous n'êtes pas encore familier avec les monades (quelque chose que vous devriez changer bientôt à mon humble avis), ignorez l'opérateur >>= Et concentrez-vous sur le let. Notez également que l'indentation n'a plus d'importance.

main = readFile "p8.log" >>= (\t ->
  let digits = map digitToInt $ concat $ lines t
  in print (problem_8 digits))
7
user395760

Quelques notes de débutant sur "suivent les mêmes deux".

Par exemple, add1 est une fonction qui ajoute 1 au nombre:

add1 :: Int -> Int
add1 x =
    let inc = 1
    in x + inc

Donc, c'est comme add1 x = x + inc avec substitution inc par 1 du mot clé let.

Lorsque vous essayez de supprimer le mot clé in

add1 :: Int -> Int
add1 x =
    let inc = 1
    x + inc

vous avez une erreur d'analyse.

De documentation :

Within do-blocks or list comprehensions 
let { d1 ; ... ; dn } 
without `in` serves to introduce local bindings. 

Btw, il y a Belle explication avec de nombreux exemples sur ce que font réellement les mots clés where et in.