۲۶ - ۱۱الگوهای بنگ

بعضی اوقات لازم میشه آرگومانِ یه تابع رو، بدونِ اهمیت به اینکه استفاده میشه یا نه، محاسبه کنیم. این کار رو میشه مثل زیر، با ‏‎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‎‏ دارین؛ و احضار این مقادیر خیلی کم هزینه‌ست. پس اگه مقادیری دارین که هم محاسبه‌شون کم هزینه‌ست و هم کوچیک‌اند، بهتره اکید‌ِشون کنین، مگر اینکه دور و بَرِ تهی می‌گردین.

تنبل در ستون، اکید در برگ! یه قاعده‌ی کلی که اکثر مواقع جواب میده.