۲۶ - ۱۱الگوهای بنگ
بعضی اوقات لازم میشه آرگومانِ یه تابع رو، بدونِ اهمیت به اینکه استفاده میشه یا نه، محاسبه کنیم. این کار رو میشه مثل زیر، با seq
انجام داد:
{-# LANGUAGE BangPatterns #-}
module ManualBang where
doesntEval :: Bool -> Int
doesntEval b = 1
manualSeq :: Bool -> Int
manualSeq b = b `seq` 1
با الگوی بنگ روی b
هم میشه این کار رو کرد – به اون علامت تعجب دقت کنین:
banging :: Bool -> Int
banging !b = 1
یه نگاه به Core برای اون سهتا بندازیم:
doesntEval
doesntEval =
\ _ -> I# 1#
manualSeq
manualSeq =
\ b_a1ia ->
case b_a1ia of _
{ __DEFAULT -> I# 1# }
banging
banging =
\ b_a1ib ->
case b_a1ib of _
{ __DEFAULT -> I# 1# }
اگه به سهتاشون تهی بدین، میبینید با اینکه manualSeq
و banging
از آرگومانشون استفادهای نمیکنن، اجبارش میکنن. یادتون باشه که اجبارِ یه چیز در Core به عنوان یه بیانیهی case بیان میشه، و اینکه case در Core تا حالت معمولی با سر ضعیف ساده میشه.
الگوی بنگ در دادهها
وقتی دادهساز ِ بیرونیِ یه نوعداده رو حساب میکنیم، گاهی اوقات میخوایم محتویاتش هم تا حالت معمولی با سر ضعیف حساب بشن.
یکی از راههایی که میشه تفاوت بین اکید و نااکید بودنِ آرگومانهای دادهسازها رو دید، بررسیِ رفتارشون در صورتِ تهی بودنه. یه مثال ببینیم (به علامت تعجب دقت کنین):
type Foo = Foo Int !Int
first (Foo x _) = x
second (Foo _ y) = y
از اونجا که آرگومانِ نااکید توسطِ second
محاسبه نمیشه، اعمالش به تهی مشکلی ایجاد نمیکنه:
> second (Foo undefined 1)
1
اما آرگومانِ اکید نمیتونه تعریفنشده باشه؛ حتی اگه از مقدارش استفاده نکنیم:
> first (Foo 1 undefined)
*** Exception: Prelude.undefined
بصورت دستی هم میشه این کار رو با seq
انجام داد، ولی خوب اینطوری راحتتره. یه مثالِ دیگه با دوتا نوعداده ِ معادلِ همدیگه، که محتویاتِ یکی از اونها اکید ِه، و محتویاتِ اون یکی اکید نیست:
{-# LANGUAGE BangPatterns #-}
module ManualBang where
data DoesntForce =
TisLazy Int String
gibString :: DoesntForce -> String
gibString (TisLazy _ s) = s
-- باز هم به علامتهای تعجب دقت کنین
data BangBang =
SheShotMeDown !Int !String
gimmeString :: BangBang -> String
gimmeString (SheShotMeDown _ s) = s
در GHCi:
Prelude> let x = TisLazy undefined "blah"
Prelude> gibString x
"blah"
Prelude> let s = SheShotMeDown
Prelude> let x = s undefined "blah"
Prelude> gimmeString x
"*** Exception: Prelude.undefined
بطور کلی در بعضی موارد، محاسبهی یه چیز در همون لحظه کم هزینهتر از اینه که یه ثانک ساخته و بعد حساب بشه. چنین موردی در خصوصِ اعداد خیلی پیش میاد، مثل وقتهایی که تعدادِ زیادی مقادیرِ Int
و Double
دارین؛ و احضار این مقادیر خیلی کم هزینهست. پس اگه مقادیری دارین که هم محاسبهشون کم هزینهست و هم کوچیکاند، بهتره اکیدِشون کنین، مگر اینکه دور و بَرِ تهی میگردین.
تنبل در ستون، اکید در برگ! یه قاعدهی کلی که اکثر مواقع جواب میده.