Haskell: Where vs. Let


117

Tôi mới sử dụng Haskell và tôi rất bối rối trước Where vs. Let . Cả hai dường như cung cấp một mục đích tương tự. Tôi đã đọc một số so sánh giữa WhereLet nhưng tôi gặp khó khăn khi phân biệt khi nào sử dụng mỗi. Ai đó có thể vui lòng cung cấp một số ngữ cảnh hoặc có lẽ một vài ví dụ chứng minh khi nào sử dụng cái này thay cho cái kia không?

Where vs. Let

Một wheremệnh đề chỉ có thể được định nghĩa ở cấp độ của một định nghĩa hàm. Thông thường, điều đó giống với phạm vi letđịnh nghĩa. Sự khác biệt duy nhất là khi các vệ sĩ được sử dụng . Phạm vi của wheređiều khoản mở rộng trên tất cả các vệ sĩ. Ngược lại, phạm vi của một letbiểu thức chỉ là mệnh đề chức năng hiện tại và bảo vệ, nếu có.

Trang Cheat Haskell

Các Haskell Wiki là rất chi tiết và cung cấp các trường hợp khác nhau nhưng nó sử dụng ví dụ giả thuyết. Tôi thấy những lời giải thích của nó quá ngắn gọn cho một người mới bắt đầu.

Ưu điểm của Let :

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

Control.Monad.State

sẽ không hoạt động, bởi vì nơi đề cập đến mẫu khớp f =, nơi không có x trong phạm vi. Ngược lại, nếu bạn đã bắt đầu với let, thì bạn sẽ không gặp khó khăn.

Haskell Wiki về Ưu điểm của Let

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

Ưu điểm ở đâu :

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)

Khai báo so với Biểu thức

Haskell wiki đề cập rằng mệnh đề Where là khai báo trong khi biểu thức Let là biểu cảm. Ngoài phong cách, chúng hoạt động khác nhau như thế nào?

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. Trong ví dụ đầu tiên, tại sao phạm vi Let in nhưng Where không?
  2. Có thể áp dụng Where cho ví dụ đầu tiên không?
  3. Một số có thể áp dụng điều này cho các ví dụ thực tế trong đó các biến đại diện cho các biểu thức thực tế không?
  4. Có nguyên tắc chung nào cần tuân theo khi sử dụng từng loại không?

Cập nhật

Đối với những người theo chủ đề này sau này, tôi tìm thấy lời giải thích tốt nhất ở đây: " Giới thiệu nhẹ nhàng về Haskell ".

Hãy biểu thức.

Biểu thức let của Haskell hữu ích bất cứ khi nào yêu cầu một tập hợp các ràng buộc lồng nhau. Như một ví dụ đơn giản, hãy xem xét:

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

Tập hợp các ràng buộc được tạo bởi một biểu thức let là đệ quy lẫn nhau và các liên kết mẫu được coi là các mẫu lười (tức là chúng mang một dấu ~ ngầm định). Loại khai báo duy nhất được phép là chữ ký kiểu, liên kết hàm và liên kết mẫu.

Điều khoản ở đâu.

Đôi khi, rất thuận tiện để xác định phạm vi ràng buộc qua một số phương trình được bảo vệ, điều này yêu cầu mệnh đề where:

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

Lưu ý rằng điều này không thể được thực hiện với biểu thức let, biểu thức này chỉ bao gồm biểu thức mà nó bao gồm. Mệnh đề where chỉ được phép ở cấp cao nhất của một tập phương trình hoặc biểu thức trường hợp. Các thuộc tính và ràng buộc giống nhau về ràng buộc trong biểu thức let áp dụng cho các thuộc tính trong mệnh đề where. Hai dạng phạm vi lồng nhau này có vẻ rất giống nhau, nhưng hãy nhớ rằng biểu thức let là một biểu thức, trong khi mệnh đề where thì không - nó là một phần của cú pháp khai báo hàm và biểu thức trường hợp.


9
Tôi đã phân vân bởi sự khác biệt giữa letwherekhi tôi lần đầu tiên bắt đầu học Haskell. Tôi nghĩ cách tốt nhất để hiểu nó là nhận ra rằng có rất ít sự khác biệt giữa hai điều này, và do đó không có gì phải lo lắng. Ý nghĩa của wheređược cho về mặt letthông qua một phép biến đổi cơ học rất đơn giản. Thực ra, hãy xem haskell.org/onlinereport/decls.html#sect4.4.3.2 Sự chuyển đổi này chỉ tồn tại vì sự tiện lợi về mặt ký hiệu.
Tom Ellis

Tôi thường sử dụng cái này hoặc cái kia tùy thuộc vào những gì tôi muốn xác định trước. Ví dụ, mọi người thường sử dụng các hàm, và sau đó xác định chúng ở đâu. Let được sử dụng nếu người ta muốn một loại hàm tìm kiếm bắt buộc.
PyRulez

@Tom Ellis, Tom, tôi đang cố gắng hiểu liên kết mà bạn đề cập đến nhưng nó quá khó đối với tôi, bạn có thể giải thích sự biến đổi đơn giản này với người phàm được không?
jhegedus

1
@jhegedus: f = body where x = xbody; y = ybody ...có nghĩa làf = let x = xbody; y = ybody ... in body
Tom Ellis

Cảm ơn Tom! Nó có thể đi theo hướng khác? Có thể biến đổi một biểu thức let thành một case .... of ... wherebiểu thức bằng cách nào đó không? Tôi không chắc chắn về điều này.
jhegedus

Câu trả lời:


39

1: Vấn đề trong ví dụ

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

là tham số x. Những thứ trong wheremệnh đề chỉ có thể tham chiếu đến các tham số của hàm f(không có hàm nào) và những thứ trong phạm vi bên ngoài.

2: Để sử dụng a wheretrong ví dụ đầu tiên, bạn có thể giới thiệu một hàm được đặt tên thứ hai có tên xlà tham số, như sau:

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

hoặc như thế này:

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

3: Đây là một ví dụ hoàn chỉnh mà không có ...'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: Khi nào sử dụng lethoặc wherelà một vấn đề của hương vị. Tôi sử dụng letđể nhấn mạnh một phép tính (bằng cách di chuyển nó ra phía trước) và wheređể nhấn mạnh luồng chương trình (bằng cách di chuyển phép tính ra phía sau).


2
"Những thứ trong mệnh đề where chỉ có thể tham chiếu đến các tham số của hàm f (không có hàm nào) và những thứ trong phạm vi bên ngoài." - Điều đó thực sự giúp làm rõ điều đó cho tôi.

28

Mặc dù có sự khác biệt về mặt kỹ thuật đối với những người bảo vệ mà con người đã chỉ ra, cũng có sự khác biệt về khái niệm về việc bạn muốn đặt công thức chính trước với các biến phụ được xác định bên dưới ( where) hay bạn muốn xác định trước mọi thứ và đặt công thức dưới đây ( let). Mỗi kiểu có một điểm nhấn khác nhau và bạn thấy cả hai đều được sử dụng trong các bài toán, sách giáo khoa, v.v. Nói chung, các biến đủ không trực quan mà công thức không có ý nghĩa nếu không có chúng nên được xác định ở trên; các biến trực quan do ngữ cảnh hoặc tên của chúng nên được định nghĩa bên dưới. Ví dụ, trong ví dụ hasVowel của ephemient, ý nghĩa của vowelslà hiển nhiên và vì vậy nó không cần phải được xác định ở trên cách sử dụng của nó (bỏ qua thực tế là nó letsẽ không hoạt động do người bảo vệ).


1
Điều này cung cấp một quy tắc ngón tay cái tốt. Bạn có thể giải thích tại sao let dường như phạm vi khác với ở đâu không?

Bởi vì cú pháp Haskell nói như vậy. Xin lỗi, không có câu trả lời hay. Có thể các định nghĩa trong phạm vi hàng đầu khó đọc khi được đặt dưới "let" nên nó không được phép.
gdj

13

Hợp pháp:

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

Không hợp pháp:

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

Hợp pháp:

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

Không hợp pháp: (không giống ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...

13
Bạn có thể giải thích tại sao các ví dụ sau đây là hợp lệ trong khi các ví dụ khác thì không?

2
Đối với những người không biết, điều này là hợp pháp: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mlà newline)
Thomas Việc chỉnh sửa

5

Tôi thấy ví dụ này từ LYHFGG hữu ích:

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

letlà một biểu thức để bạn có thể đặt một let (!)bất kỳ đâu mà các biểu thức có thể đi đến.

Nói cách khác, trong ví dụ trên, không thể sử dụng wheređể thay thế đơn giản let(mà không có lẽ sử dụng một số casebiểu thức dài dòng hơn kết hợp với where).


3

Đáng buồn thay, hầu hết các câu trả lời ở đây đều quá kỹ thuật đối với người mới bắt đầu.

LHYFGG có một chương liên quan về nó - bạn nên đọc nếu bạn chưa đọc, nhưng về bản chất:

  • wherechỉ là một cấu trúc cú pháp (không phải là một đường ) chỉ hữu ích ở các định nghĩa hàm .
  • let ... inchính là một biểu thức , do đó bạn có thể sử dụng chúng ở bất cứ nơi nào bạn có thể đặt một biểu thức. Bản thân nó là một biểu thức, nó không thể được sử dụng để ràng buộc những thứ cho lính canh.

Cuối cùng, bạn cũng có thể sử dụng lettrong phần hiểu danh sách:

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

Chúng tôi bao gồm một let bên trong phần hiểu danh sách giống như chúng tôi làm với một vị từ, chỉ là nó không lọc danh sách, nó chỉ liên kết với tên. Các tên được xác định bằng chữ cái bên trong khả năng hiểu danh sách được hiển thị cho hàm đầu ra (phần trước dấu |) và tất cả các vị từ và phần đứng sau liên kết. Vì vậy, chúng tôi có thể làm cho hàm của chúng tôi chỉ trả về chỉ số BMI của những người> = 25:


Đây là câu trả lời duy nhất thực sự giúp tôi hiểu được sự khác biệt. Mặc dù các câu trả lời kỹ thuật có thể hữu ích cho một Haskell-er giàu kinh nghiệm hơn, nhưng điều này đủ ngắn gọn cho một người mới bắt đầu như tôi! +1
Zac G
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.