Haskell: ที่ไหนกับให้


118

ฉันใหม่เพื่อ Haskell และฉันสับสนมากจากไหนเมื่อเทียบกับLet ดูเหมือนทั้งสองจะมีจุดประสงค์ที่คล้ายกัน ฉันได้อ่านการเปรียบเทียบเล็กน้อยระหว่างWhereกับLetแต่ฉันมีปัญหาในการแยกแยะว่าเมื่อใดควรใช้ ใครช่วยกรุณาให้บริบทบางอย่างหรืออาจเป็นตัวอย่างบางส่วนที่แสดงให้เห็นว่าเมื่อใดควรใช้อย่างอื่น

ที่ไหนเทียบกับ

whereประโยคสามารถกำหนดเฉพาะในระดับของการกำหนดฟังก์ชั่น โดยปกติแล้วจะเหมือนกับขอบเขตของletคำจำกัดความ ความแตกต่างเพียงอย่างเดียวคือเมื่อยามที่มีการใช้ ขอบเขตของwhereประโยคขยายครอบคลุมยามทั้งหมด ในทางตรงกันข้ามขอบเขตของletนิพจน์เป็นเพียงประโยคฟังก์ชันปัจจุบันและตัวป้องกันถ้ามี

แผ่นโกง Haskell

Haskell วิกิพีเดียมีรายละเอียดมากและให้หลาย ๆ กรณี แต่จะใช้ตัวอย่างสมมุติ ฉันพบว่าคำอธิบายสั้นเกินไปสำหรับผู้เริ่มต้น

ข้อดีของการให้ :

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

Control.Monad.State

จะไม่ทำงานเนื่องจากโดยที่อ้างถึงรูปแบบที่ตรงกับ f = โดยที่ x ไม่อยู่ในขอบเขต ในทางตรงกันข้ามถ้าคุณเริ่มต้นด้วยการปล่อยให้คุณก็จะไม่มีปัญหา

Haskell Wiki เกี่ยวกับข้อดีของการให้

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

ข้อดีของที่ไหน :

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)

การประกาศเทียบกับนิพจน์

วิกิฮาสเคลกล่าวว่าประโยคWhereถูกประกาศในขณะที่นิพจน์Letเป็นการแสดงออก นอกเหนือจากสไตล์แล้วพวกเขาทำงานแตกต่างกันอย่างไร?

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. ในตัวอย่างแรกเหตุใดLet จึงอยู่ในขอบเขต แต่ไม่อยู่ที่ไหน
  2. เป็นไปได้ไหมที่จะใช้Whereกับตัวอย่างแรก
  3. บางคนสามารถใช้สิ่งนี้กับตัวอย่างจริงที่ตัวแปรแสดงถึงนิพจน์จริงได้หรือไม่?
  4. มีกฎทั่วไปที่ต้องปฏิบัติตามเมื่อใช้แต่ละข้อหรือไม่?

ปรับปรุง

สำหรับผู้ที่มาจากหัวข้อนี้ในภายหลังฉันพบคำอธิบายที่ดีที่สุดที่นี่: " A Gentle Introduction to Haskell "

ให้นิพจน์

นิพจน์ let ของ Haskell มีประโยชน์เมื่อใดก็ตามที่จำเป็นต้องใช้ชุดการผูกที่ซ้อนกัน เป็นตัวอย่างง่ายๆให้พิจารณา:

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

ชุดของการผูกที่สร้างขึ้นโดยนิพจน์ let นั้นเป็นแบบวนซ้ำร่วมกันและการผูกรูปแบบจะถือว่าเป็นรูปแบบที่ขี้เกียจ (กล่าวคือมีนัย ~) การประกาศประเภทเดียวที่อนุญาตคือลายเซ็นประเภทการผูกฟังก์ชันและการผูกรูปแบบ

ที่ข้อ

บางครั้งมันก็สะดวกในการกำหนดขอบเขตการผูกกับสมการที่มีการป้องกันหลาย ๆ อย่างซึ่งต้องใช้คำสั่ง where:

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

โปรดทราบว่าสิ่งนี้ไม่สามารถทำได้ด้วยนิพจน์ let ซึ่งขอบเขตเหนือนิพจน์ที่ล้อมรอบเท่านั้น โดยที่อนุประโยคได้รับอนุญาตที่ระดับบนสุดของชุดสมการหรือนิพจน์กรณีเท่านั้น คุณสมบัติและข้อ จำกัด เดียวกันในการผูกในนิพจน์ let ใช้กับคุณสมบัติที่อยู่ในส่วนคำสั่ง ขอบเขตที่ซ้อนกันทั้งสองรูปแบบนี้ดูคล้ายกันมาก แต่จำไว้ว่านิพจน์ let เป็นนิพจน์ในขณะที่ส่วนคำสั่งไม่ใช่ - เป็นส่วนหนึ่งของไวยากรณ์ของการประกาศฟังก์ชันและนิพจน์กรณี


9
ฉันรู้สึกงงงวยกับความแตกต่างระหว่างletและwhereเมื่อฉันเริ่มเรียนรู้ Haskell ครั้งแรก ฉันคิดว่าวิธีที่ดีที่สุดในการทำความเข้าใจคือการตระหนักว่าทั้งสองมีความแตกต่างกันเล็กน้อยดังนั้นจึงไม่มีอะไรต้องกังวล ความหมายของการwhereให้ในแง่ของ letการแปลงทางกลที่เรียบง่ายมาก ดูhaskell.org/onlinereport/decls.html#sect4.4.3.2 การ เปลี่ยนแปลงนี้มีขึ้นเพื่อความสะดวกในการระบุเท่านั้นจริงๆ
Tom Ellis

โดยทั่วไปฉันจะใช้อย่างใดอย่างหนึ่งขึ้นอยู่กับสิ่งที่ฉันต้องการกำหนดก่อน ตัวอย่างเช่นผู้คนมักใช้ฟังก์ชันแล้วกำหนดไว้ในตำแหน่งที่ Let ถูกใช้หากต้องการฟังก์ชันที่ดูจำเป็น
PyRulez

@ ทอมเอลลิสทอมฉันพยายามเข้าใจลิงค์ที่คุณอ้างถึง แต่มันยากเกินไปสำหรับฉันคุณช่วยอธิบายการเปลี่ยนแปลงง่ายๆนี้ให้เป็นแค่มนุษย์ได้ไหม
jhegedus

1
@jhegedus: f = body where x = xbody; y = ybody ...หมายถึงf = let x = xbody; y = ybody ... in body
ทอมเอลลิส

ขอบคุณทอม! มันไปทางอื่นได้ไหม? เป็นไปได้ไหมที่จะเปลี่ยนนิพจน์ let เป็นcase .... of ... whereนิพจน์ได้หรือไม่? ฉันไม่แน่ใจเกี่ยวกับเรื่องนี้
jhegedus

คำตอบ:


39

1: ปัญหาในตัวอย่าง

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

xเป็นพารามิเตอร์ สิ่งต่างๆในwhereประโยคสามารถอ้างถึงพารามิเตอร์ของฟังก์ชันเท่านั้นf(ไม่มีเลย) และสิ่งที่อยู่ในขอบเขตภายนอก

2: ในการใช้ a whereในตัวอย่างแรกคุณสามารถแนะนำฟังก์ชันที่มีชื่อที่สองซึ่งใช้xเป็นพารามิเตอร์ดังนี้:

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

หรือเช่นนี้:

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

3: นี่คือตัวอย่างที่สมบูรณ์โดยไม่ต้อง...มี:

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: เมื่อใดควรใช้letหรือwhereเป็นเรื่องของรสนิยม ฉันใช้letเพื่อเน้นการคำนวณ (โดยเลื่อนไปด้านหน้า) และwhereเพื่อเน้นผังรายการ (โดยย้ายการคำนวณไปด้านหลัง)


2
"สิ่งที่อยู่ในอนุประโยคสามารถอ้างถึงเฉพาะพารามิเตอร์ของฟังก์ชัน f (ไม่มีเลย) และสิ่งที่อยู่ในขอบเขตภายนอก" - นั่นช่วยชี้แจงให้ฉันได้จริงๆ

28

ในขณะที่มีความแตกต่างทางเทคนิคเกี่ยวกับยามที่ระบุไว้ชั่วคราว แต่ก็มีความแตกต่างในเชิงแนวคิดว่าคุณต้องการวางสูตรหลักไว้ตรงหน้าด้วยตัวแปรพิเศษที่กำหนดไว้ด้านล่าง ( where) หรือว่าคุณต้องการกำหนดทุกอย่างล่วงหน้าและใส่สูตร ด้านล่าง ( let) แต่ละสไตล์มีการเน้นที่แตกต่างกันและคุณจะเห็นทั้งสองแบบที่ใช้ในเอกสารคณิตศาสตร์หนังสือเรียน ฯลฯ โดยทั่วไปตัวแปรที่ไม่เข้าใจง่ายเพียงพอที่สูตรจะไม่สมเหตุสมผลหากไม่มีควรกำหนดไว้ข้างต้น ตัวแปรที่ใช้งานง่ายเนื่องจากบริบทหรือชื่อควรกำหนดไว้ด้านล่าง ตัวอย่างเช่นในตัวอย่าง hasVowel ของ ephemient ความหมายของvowelsมันชัดเจนและไม่จำเป็นต้องกำหนดไว้เหนือการใช้งาน (โดยไม่คำนึงถึงความจริงที่letจะไม่ทำงานเนื่องจากตัวป้องกัน)


1
นี่เป็นหลักการที่ดี คุณช่วยอธิบายให้ละเอียดได้ไหมว่าเหตุใดการให้จึงดูเหมือนมีขอบเขตแตกต่างจากที่ใด

เพราะไวยากรณ์ของ Haskell พูดอย่างนั้น ขออภัยไม่มีคำตอบที่ดี บางทีคำจำกัดความที่มีขอบเขตบนสุดอาจอ่านยากเมื่อซ่อนอยู่ภายใต้เครื่องหมาย "let" จึงไม่ได้รับอนุญาต
gdj

13

กฎหมาย:

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

ไม่ถูกกฎหมาย:

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

กฎหมาย:

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

ไม่ถูกกฎหมาย: (ไม่เหมือน ML)

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

13
คุณสามารถอธิบายได้หรือไม่ว่าเหตุใดตัวอย่างต่อไปนี้จึงใช้ได้ในขณะที่อีกตัวอย่างไม่ถูกต้อง

2
สำหรับผู้ที่ไม่ทราบนี่เป็นกฎหมาย: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mเป็นบรรทัดใหม่)
Thomas Eding

5

ฉันพบว่าตัวอย่างนี้จากLYHFGG มีประโยชน์:

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

letเป็นนิพจน์เพื่อให้คุณสามารถใส่let ทุกที่ (!)ที่นิพจน์ไปได้

กล่าวอีกนัยหนึ่งในตัวอย่างข้างต้นไม่สามารถใช้whereเพื่อแทนที่เพียงอย่างเดียวได้let(โดยไม่ต้องใช้caseนิพจน์เพิ่มเติมบางอย่างรวมกับwhere)


3

น่าเสียดายที่คำตอบส่วนใหญ่ในที่นี้เป็นเทคนิคสำหรับมือใหม่เกินไป

LHYFGGมีบทที่เกี่ยวข้องซึ่งคุณควรอ่านหากคุณยังไม่ได้อ่าน แต่โดยพื้นฐานแล้ว:

  • whereเป็นเพียงการสร้างประโยค (ไม่ได้เป็นน้ำตาล ) ที่มีประโยชน์เฉพาะในคำจำกัดความของฟังก์ชั่น
  • let ... inเป็นนิพจน์เองดังนั้นคุณสามารถใช้มันได้ทุกที่ที่คุณใส่นิพจน์ การแสดงออกเองจึงไม่สามารถใช้เพื่อผูกมัดสิ่งของสำหรับองครักษ์ได้

สุดท้ายนี้คุณสามารถใช้letในการทำความเข้าใจรายการได้เช่นกัน:

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

เรารวมการให้ไว้ในความเข้าใจของรายการเหมือนกับที่เราเป็นเพรดิเคตเพียง แต่ไม่ได้กรองรายการ แต่จะเชื่อมโยงกับชื่อเท่านั้น ชื่อที่กำหนดไว้ใน let ภายในความเข้าใจของรายการจะมองเห็นได้ในฟังก์ชันเอาต์พุต (ส่วนก่อนหน้า|) และเพรดิเคตและส่วนทั้งหมดที่มาหลังจากการโยง ดังนั้นเราสามารถทำให้ฟังก์ชันของเราส่งคืนเฉพาะค่าดัชนีมวลกายของคน> = 25:


นี่เป็นคำตอบเดียวที่ช่วยให้ฉันเข้าใจความแตกต่างได้จริง แม้ว่าคำตอบทางเทคนิคอาจเป็นประโยชน์สำหรับ Haskell-er ที่มีประสบการณ์สูง แต่ก็สั้นพอสำหรับผู้เริ่มต้นอย่างฉัน! +1
Zac G
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.