Haskell: Where vs. Let

117

Saya baru mengenal Haskell dan saya sangat bingung dengan Where vs. Let . Keduanya sepertinya memberikan tujuan yang sama. Saya telah membaca beberapa perbandingan antara Where vs. Let tetapi saya kesulitan membedakan kapan harus menggunakan masing-masing. Bisakah seseorang memberikan beberapa konteks atau mungkin beberapa contoh yang menunjukkan kapan harus menggunakan salah satu dari yang lain?

Dimana vs.

Sebuah whereklausul hanya dapat didefinisikan pada tingkat definisi fungsi. Biasanya, itu identik dengan ruang lingkup letdefinisi. Satu-satunya perbedaan adalah saat penjaga digunakan . Cakupan whereklausul meluas ke semua penjaga. Sebaliknya, cakupan letekspresi hanya klausa fungsi saat ini dan penjaga, jika ada.

Lembar Cheat Haskell

The Haskell Wiki sangat rinci dan menyediakan berbagai kasus tetapi menggunakan contoh hipotetis. Saya merasa penjelasannya terlalu singkat untuk pemula.

Keuntungan dari Let :

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

Control.Monad.State

tidak akan bekerja, karena where mengacu pada pola yang cocok dengan f =, di mana tidak ada x dalam ruang lingkup. Sebaliknya, jika Anda memulai dengan biarkan, maka Anda tidak akan mengalami kesulitan.

Haskell Wiki tentang Keuntungan Let

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

Keuntungan Dimana :

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)

Deklarasi vs. Ekspresi

Wiki Haskell menyebutkan bahwa klausa Where bersifat deklaratif sedangkan ekspresi Let ekspresif. Selain gaya, bagaimana mereka tampil berbeda?

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. Pada contoh pertama mengapa Let in scope tapi Where tidak?
  2. Apakah mungkin untuk menerapkan Di mana contoh pertama?
  3. Bisakah beberapa menerapkan ini pada contoh nyata di mana variabel mewakili ekspresi sebenarnya?
  4. Apakah ada aturan umum yang harus diikuti kapan harus menggunakan masing-masing?

Memperbarui

Bagi mereka yang datang melalui utas ini nanti, saya menemukan penjelasan terbaik dapat ditemukan di sini: " Pengantar yang Lembut untuk Haskell ".

Biarkan Ekspresi.

Ekspresi let Haskell berguna setiap kali diperlukan kumpulan bertingkat dari binding. Sebagai contoh sederhana, pertimbangkan:

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

Himpunan binding yang dibuat oleh ekspresi let saling rekursif, dan binding pola diperlakukan sebagai pola malas (yaitu, membawa ~ implisit). Satu-satunya jenis deklarasi yang diizinkan adalah tanda tangan tipe, binding fungsi, dan binding pola.

Dimana Klausul.

Kadang-kadang lebih mudah untuk mengikat lingkup pada beberapa persamaan yang dilindungi, yang membutuhkan klausa where:

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

Perhatikan bahwa ini tidak bisa dilakukan dengan ekspresi let, yang hanya mencakup ekspresi yang diapitnya. Klausa where hanya diperbolehkan di tingkat atas dari kumpulan persamaan atau ekspresi kasus. Properti dan batasan yang sama pada binding di biarkan ekspresi diterapkan ke dalam klausa where. Kedua bentuk lingkup bersarang ini tampak sangat mirip, tetapi ingat bahwa ekspresi let adalah ekspresi, sedangkan klausa where bukan - itu adalah bagian dari sintaks deklarasi fungsi dan ekspresi kasus.

nbro
sumber
9
Saya bingung dengan perbedaan antara letdan wheresaat pertama kali belajar Haskell. Saya pikir cara terbaik untuk memahaminya adalah dengan menyadari bahwa ada sangat sedikit perbedaan antara keduanya, dan oleh karena itu tidak ada yang perlu dikhawatirkan. Arti yang wherediberikan dalam pengertian letmelalui transformasi mekanis yang sangat sederhana. Lihat haskell.org/onlinereport/decls.html#sect4.4.3.2 Transformasi ini ada hanya untuk kenyamanan notasi.
Tom Ellis
Saya biasanya menggunakan satu atau yang lain tergantung pada apa yang ingin saya definisikan terlebih dahulu. Misalnya, orang sering menggunakan fungsi, lalu menentukannya di mana. Let digunakan jika seseorang menginginkan semacam fungsi tampilan imperatif.
PyRulez
@ Tom Ellis, Tom, saya mencoba untuk memahami tautan yang Anda rujuk tetapi itu terlalu sulit bagi saya, dapatkah Anda menjelaskan transformasi sederhana ini kepada manusia biasa?
jhegedus
1
@jhegedus: f = body where x = xbody; y = ybody ...artinyaf = let x = xbody; y = ybody ... in body
Tom Ellis
Terima kasih Tom! Bisakah itu sebaliknya? Apakah mungkin untuk mengubah ekspresi let menjadi case .... of ... whereekspresi? Saya tidak yakin tentang ini.
jhegedus

Jawaban:

39

1: Masalah di contoh

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

adalah parameternya x. Hal-hal dalam whereklausa hanya dapat merujuk ke parameter fungsi f(tidak ada) dan hal-hal di cakupan luar.

2: Untuk menggunakan a wherepada contoh pertama, Anda dapat memperkenalkan fungsi bernama kedua yang mengambil xparameter sebagai, seperti ini:

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

atau seperti ini:

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

3: Berikut adalah contoh lengkap tanpa ...'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: Kapan harus digunakan letatau wheretergantung selera. Saya menggunakan letuntuk menekankan komputasi (dengan memindahkannya ke depan) dan whereuntuk menekankan aliran program (dengan memindahkan komputasi ke belakang).

antonakos
sumber
2
"Hal-hal di klausa where hanya dapat merujuk ke parameter fungsi f (tidak ada) dan hal-hal di cakupan luar." - Itu sangat membantu menjelaskannya untukku.
28

Meskipun ada perbedaan teknis sehubungan dengan pelindung yang ditunjukkan oleh sementara, ada juga perbedaan konseptual dalam hal apakah Anda ingin meletakkan rumus utama di muka dengan variabel tambahan yang ditentukan di bawah ( where) atau apakah Anda ingin mendefinisikan semuanya di muka dan meletakkan rumusnya di bawah ( let). Setiap gaya memiliki penekanan yang berbeda dan Anda melihat keduanya digunakan dalam makalah matematika, buku teks, dll. Umumnya, variabel yang cukup tidak intuitif sehingga rumus tidak masuk akal tanpanya harus didefinisikan di atas; variabel yang intuitif karena konteks atau namanya harus ditentukan di bawah. Misalnya, dalam contoh hasVowel ephemient, artinya vowelssudah jelas dan karenanya tidak perlu didefinisikan di atas penggunaannya (mengabaikan fakta bahwa lettidak akan berfungsi karena penjaga).

gdj
sumber
1
Ini memberikan aturan praktis yang baik. Bisakah Anda menguraikan mengapa biarkan tampaknya ruang lingkup berbeda dari mana?
Karena sintaks Haskell mengatakan demikian. Maaf, tidak ada jawaban yang bagus. Mungkin definisi cakupan atas sulit untuk dibaca jika dimasukkan ke dalam tanda "biarkan" sehingga tidak diizinkan.
gdj
13

Hukum:

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

Tidak sah:

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

Hukum:

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

Tidak legal: (tidak seperti ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...
efemient
sumber
13
Dapatkah Anda menjelaskan mengapa contoh berikut ini valid sedangkan yang lainnya tidak?
2
Bagi mereka yang tidak tahu, ini legal: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mbaris baru)
Thomas Eding
5

Saya menemukan contoh dari LYHFGG ini bermanfaat:

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

letadalah ekspresi sehingga Anda dapat meletakkannya di let mana saja (!) mana ekspresi bisa pergi.

Dengan kata lain, dalam contoh di atas tidak mungkin digunakan whereuntuk sekadar mengganti let(tanpa mungkin menggunakan beberapa caseekspresi verbose yang digabungkan dengan where).

jhegedus.dll
sumber
3

Sayangnya, sebagian besar jawaban di sini terlalu teknis untuk pemula.

LHYFGG memiliki bab yang relevan -yang harus Anda baca jika Anda belum melakukannya, tetapi pada intinya:

  • wherehanyalah konstruksi sintaksis (bukan gula ) yang hanya berguna pada definisi fungsi .
  • let ... inadalah ekspresi itu sendiri , sehingga Anda dapat menggunakannya di mana pun Anda dapat meletakkan ekspresi. Menjadi ekspresi itu sendiri, itu tidak bisa digunakan untuk mengikat sesuatu untuk penjaga.

Terakhir, Anda juga dapat menggunakan letdalam pemahaman daftar:

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

Kami menyertakan biarkan di dalam daftar pemahaman seperti kami akan predikat, hanya itu tidak memfilter daftar, itu hanya mengikat nama. Nama-nama yang didefinisikan dalam pemahaman let inside a list terlihat oleh fungsi output (bagian sebelum |) dan semua predikat dan bagian yang muncul setelah pengikatan. Jadi kita bisa membuat fungsi kita hanya mengembalikan BMI orang> = 25:

Bora M. Alper
sumber
Ini adalah satu-satunya jawaban yang benar-benar membantu saya memahami perbedaannya. Meskipun jawaban teknisnya mungkin berguna untuk Haskell-er yang lebih berpengalaman, ini cukup ringkas untuk pemula seperti saya! +1
Zac G