Nice programing

Haskell에서 언제 let과 함께 사용합니까?

nicepro 2021. 1. 5. 21:11
반응형

Haskell에서 언제 let과 함께 사용합니까?


다음 코드에서 내가 in앞에 넣을 수있는 마지막 문구 입니다. 어떤 것이 바뀌나요?

또 다른 질문 : in마지막 문구 앞에 놓기 결정한 경우 들여 쓰기를해야합니까?

들여 쓰기없이 시도했는데 안아 불평

do {...}의 마지막 생성기는 표현식이어야합니다.

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

편집하다

좋아, 그래서 사람들은 내가 말하는 것을 이해하지 못하는 것 같습니다. 다시 말하겠습니다 : 위의 맥락에서 다음 두 가지는 동일한가요?

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

선언 된 바인딩의 범위에 관한 또 다른 질문 let: 여기 에서 읽었 습니다 .

where 조항.

때로는 where 절이 필요한 여러 보호 방정식에 대해 바인딩 범위를 지정하는 것이 편리합니다.

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

이것은 단지 표현을 통해 스코프 렛 표현, 함께 할 수 없습니다 그것을 둘러싸는 .

내 질문 : 그래서 가변 숫자는 마지막 인쇄 문구에 표시되지 않아야합니다. 여기서 뭔가 놓친 게 있나요?


짧은 대답 : do-block의 본문에 let없이 사용 하고 목록 이해 in에서 다음 부분에 사용합니다 |. 다른 곳에서는 let ... in ....


키워드 let는 Haskell에서 세 가지 방식으로 사용됩니다.

  1. 첫 번째 형식은 let-expression 입니다.

    let variable = expression in expression
    

    표현식이 허용되는 모든 곳에서 사용할 수 있습니다.

    > (let x = 2 in x*2) + 3
    7
    
  2. 두 번째는 let-statement 입니다. 이 형식은 do-notation 내부에서만 사용되며 in.

    do statements
       let variable = expression
       statements
    
  3. 세 번째는 2 번과 유사하며 목록 내포 내에서 사용됩니다. 다시 말하지만 in.

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

    이 양식은 후속 생성기의 범위와 |.


여기서 혼란스러운 이유는 (올바른 유형의) 표현식이 do-block 내에서 명령문으로 사용될 수 있으며 let .. in ..단지 표현식이기 때문입니다.

haskell의 들여 쓰기 규칙 때문에 이전 줄보다 더 들여 쓰기 된 줄은 이전 줄의 연속임을 의미합니다.

do let x = 42 in
     foo

다음과 같이 구문 분석됩니다.

do (let x = 42 in foo)

들여 쓰기가 없으면 구문 분석 오류가 발생합니다.

do (let x = 42 in)
   foo

결론적 in으로 목록 이해 또는 do-block에서 사용하지 마십시오. 이러한 구성은 이미 자체 형식을 가지고 있기 때문에 불필요하고 혼란 스럽습니다 let.


먼저, 왜 포옹합니까? 하스켈 플랫폼은 일반적으로 GHC와 함께 제공 초보자를 위해 이동하는 권장 방법입니다.

이제 let키워드 를 살펴 보겠습니다 . 이 키워드의 가장 간단한 형태는 것을 의미한다 항상 함께 사용할 수 in.

let {assignments} in {expression}

예를 들면

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

The {assignments} are only in scope in the corresponding {expression}. Regular layout rules apply, meaning that in must be indented at least as much as the let that it corresponds to, and any sub-expressions pertaining to the let expression must likewise be indented at least as much. This isn't actually 100% true, but is a good rule of thumb; Haskell layout rules are something you will just get used to over time as you read and write Haskell code. Just keep in mind that the amount of indentation is the main way to indicate which code pertains to what expression.

Haskell provides two convenience cases where you don't have to write in: do notation and list comprehensions (actually, monad comprehensions). The scope of the assignments for these convenience cases is predefined.

do foo
   let {assignments}
   bar
   baz

For do notation, the {assignments} are in scope for any statements that follow, in this case, bar and baz, but not foo. It is as if we had written

do foo
   let {assignments}
   in do bar
         baz

List comprehensions (or really, any monad comprehension) desugar into do notation, so they provide a similar facility.

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

The {assignments} are in scope for the expressions bar and baz, but not for foo.


where is somewhat different. If I'm not mistaken, the scope of where lines up with a particular function definition. So

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

the {assignments} in this where clause have access to x and y. guard1, guard2, blah1, and blah2 all have access to the {assignments} of this where clause. As is mentioned in the tutorial you linked, this can be helpful if multiple guards reuse the same expressions.


In do notation, you can indeed use let with and without in. For it to be equivalent (in your case, I'll later show an example where you need to add a second do and thus more indentation), you need to indent it as you discovered (if you're using layout - if you use explicit braces and semicolons, they're exactly equivalent).

To understand why it's equivalent, you have to actually grok monads (at least to some degree) and look at the desugaring rules for do notation. In particular, code like this:

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

is translated to let x = ... in do { stmts }. In your case, stmts = print (problem_8 digits). Evaluating the whole desugared let binding results in an IO action (from print $ ...). And here, you need understanding of monads to intuitively agree that there's no difference between do notations and "regular" language elements describing a computation resulting in monadic values.

As for both why are possible: Well, let ... in ... has a broad range of applications (most of which have nothing to do with monads in particular), and a long history to boot. let without in for do notation, on the other hand, seems to be nothing but a small piece of syntactic sugar. The advantage is obvious: You can bind the results of pure (as in, not monadic) computations to a name without resorting to a pointless val <- return $ ... and without splitting up the do block in two:

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

The reason you don't need an extra do block for what follows the let is that you only got a single line. Remember, do e is e.

Regarding your edit: digit being visible in the next line is the whole point. And there's no exception for it or anything. do notation becomes one single expression, and let works just fine in a single expression. where is only needed for things which aren't expressions.

For the sake of demonstration, I'll show the desugared version of your do block. If you aren't too familiar with monads yet (something you should change soon IMHO), ignore the >>= operator and focus on the let. Also note that indentation doesn't matter any more.

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

Some beginner notes about "are following two the same".

For example, add1 is a function, that add 1 to number:

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

So, it's like add1 x = x + inc with substitution inc by 1 from let keyword.

When you try to suppress in keyword

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

you've got parse error.

From documentation:

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

Btw, there are nice explanation with many examples about what where and in keyword actually do.

ReferenceURL : https://stackoverflow.com/questions/8274650/in-haskell-when-do-we-use-in-with-let

반응형