Guards vs. if-then-else vs. cases ใน Haskell


104

ฉันมีฟังก์ชั่นสามอย่างที่ค้นหาองค์ประกอบที่ n ของรายการ:

nthElement :: [a] -> Int -> Maybe a 
nthElement [] a = Nothing
nthElement (x:xs) a | a <= 0 = Nothing
                    | a == 1 = Just x
                    | a > 1 = nthElement xs (a-1)

nthElementIf :: [a] -> Int -> Maybe a
nthElementIf [] a = Nothing
nthElementIf (x:xs) a = if a <= 1
                        then if a <= 0 
                             then Nothing
                             else Just x -- a == 1
                        else nthElementIf xs (a-1)                           

nthElementCases :: [a] -> Int -> Maybe a
nthElementCases [] a = Nothing
nthElementCases (x:xs) a = case a <= 0 of
                             True -> Nothing
                             False -> case a == 1 of
                                        True -> Just x
                                        False -> nthElementCases xs (a-1)

ในความคิดของฉันฟังก์ชั่นแรกคือการนำไปใช้งานที่ดีที่สุดเพราะมีความรัดกุมที่สุด แต่มีอะไรเกี่ยวกับการใช้งานอีกสองรายการที่จะทำให้ดีกว่านี้หรือไม่? คุณจะเลือกอย่างไรระหว่างการใช้ยามคำสั่ง if-then-else และกรณี?


5
คุณสามารถยุบcaseข้อความที่ซ้อนกันได้หากคุณใช้case compare a 0 of LT -> ... | EQ -> ... | GT -> ...
rampion

5
@rampion: คุณหมายถึงcase compare a 1 of ...
newacct

คำตอบ:


122

จากมุมมองทางเทคนิคทั้งสามเวอร์ชันมีความเท่าเทียมกัน

ตามที่กล่าวไว้กฎง่ายๆสำหรับรูปแบบของฉันคือถ้าคุณอ่านได้เหมือนกับว่าเป็นภาษาอังกฤษ (อ่าน|ว่า "เมื่อ" | otherwiseเป็น "อย่างอื่น" และ=เป็น "เป็น" หรือ "เป็น") คุณอาจกำลังทำบางสิ่ง ขวา.

if..then..elseมีไว้สำหรับเมื่อคุณมีเงื่อนไขไบนารีหนึ่งข้อหรือการตัดสินใจเพียงครั้งเดียวที่คุณต้องทำ การif..then..elseแสดงออกที่ซ้อนกันเป็นเรื่องแปลกมากใน Haskell และควรใช้การ์ดแทนเกือบตลอดเวลา

let absOfN =
  if n < 0 -- Single binary expression
  then -n
  else  n

ทุกif..then..elseนิพจน์สามารถถูกแทนที่ด้วยตัวป้องกันได้หากอยู่ในระดับบนสุดของฟังก์ชันและโดยทั่วไปควรเป็นที่ต้องการเนื่องจากคุณสามารถเพิ่มกรณีต่างๆได้ง่ายขึ้นจากนั้น:

abs n
  | n < 0     = -n
  | otherwise =  n

case..ofมีไว้สำหรับเมื่อคุณมีเส้นทางรหัสหลายเส้นทางและทุกเส้นทางของรหัสจะถูกชี้นำโดย โครงสร้างของค่าเช่นผ่านการจับคู่รูปแบบ คุณไม่ค่อยตรงกับในและTrueFalse

case mapping of
  Constant v -> const v
  Function f -> map f

Guards เสริมcase..ofนิพจน์ซึ่งหมายความว่าหากคุณจำเป็นต้องทำการตัดสินใจที่ซับซ้อนโดยขึ้นอยู่กับค่าก่อนอื่นให้ทำการตัดสินใจโดยขึ้นอยู่กับโครงสร้างของข้อมูลที่คุณป้อนจากนั้นทำการตัดสินใจเกี่ยวกับค่าในโครงสร้าง

handle  ExitSuccess = return ()
handle (ExitFailure code)
  | code < 0  = putStrLn . ("internal error " ++) . show . abs $ code
  | otherwise = putStrLn . ("user error " ++)     . show       $ code

BTW. ในฐานะเคล็ดลับสไตล์ให้สร้างบรรทัดใหม่หลัง=หรือก่อนหน้าเสมอ|หากสิ่งที่อยู่หลัง=/ |ยาวเกินไปสำหรับหนึ่งบรรทัดหรือใช้บรรทัดมากขึ้นด้วยเหตุผลอื่น:

-- NO!
nthElement (x:xs) a | a <= 0 = Nothing
                    | a == 1 = Just x
                    | a > 1 = nthElement xs (a-1)

-- Much more compact! Look at those spaces we didn't waste!
nthElement (x:xs) a
  | a <= 0    = Nothing
  | a == 1    = Just x
  | otherwise = nthElement xs (a-1)

1
"คุณไม่ค่อยเข้ากันTrueและFalse" มีโอกาสใดบ้างที่คุณจะทำเช่นนั้น? หลังจากที่ทุกชนิดของการตัดสินใจครั้งนี้สามารถเสมอจะทำกับifและมีเจ้าหน้าที่รักษาความปลอดภัยนอกจากนี้ยังมี
leftaround ประมาณ

2
เช่นcase (foo, bar, baz) of (True, False, False) -> ...
dflemstr

@dflemstr ไม่มีความแตกต่างที่ลึกซึ้งอีกต่อไปเช่นยามที่ต้องใช้ MonadPlus และและส่งคืนตัวอย่างของ monad ในขณะที่ถ้าอย่างอื่นไม่มี? แต่ผมไม่แน่ใจ
J Fritsch

2
@JFritsch: guardฟังก์ชั่นต้องการMonadPlusแต่สิ่งที่เรากำลังพูดถึงต่อไปนี้คือยามเช่นเดียวกับใน| test =ประโยคซึ่งไม่เกี่ยวข้องกัน
Ben Millwood

ขอบคุณสำหรับเคล็ดลับสไตล์ตอนนี้ได้รับการยืนยันด้วยข้อสงสัย
truthadjustr

22

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

nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)

2

นี่เป็นเพียงเรื่องของการสั่งซื้อ แต่ฉันคิดว่ามันอ่านง่ายมากและมีโครงสร้างแบบเดียวกับยาม

nthElement :: [a] -> Int -> Maybe a 
nthElement [] a = Nothing
nthElement (x:xs) a = if a  < 1 then Nothing else
                      if a == 1 then Just x
                      else nthElement xs (a-1)

สิ่งสุดท้ายไม่ต้องการและหากไม่มีความเป็นไปได้อื่น ๆ ฟังก์ชันควรมี "กรณีสุดท้าย" ในกรณีที่คุณพลาดอะไรไป


4
คำสั่ง if ที่ซ้อนกันเป็นรูปแบบการต่อต้านเมื่อคุณสามารถใช้ตัวป้องกันเคสได้
user76284
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.