۲ - ۱۰کلیدواژه‌های let و where

در کد هسکل از ‏‎let‎‏ و ‏‎where‎‏ برای معرفیِ بخشهایی از یک بیانیه، زیاد استفاده میشن، و خیلی هم شبیهِ هم هستن. برای استفاده‌ی به‌جا از هر کدوم، کمی تمرین لازمه.

تفاوت‌شون اینجاست که ‏‎let‎‏ یک بیانیه یا expression رو معرفی می‌کنه، پس هر جایی که بشه بیانیه نوشت قابل جاگذاری‌ه. ولی ‏‎where‎‏ یک تعریف یا declaration ِه، و به سازه‌یِ گرامری ِاَطرافش مقیّده.

با یه مثال از ‏‎where‎‏ شروع می‌کنیم:

-- FunctionWithWhere.hs
module FunctionWithWhere where

printInc n = print plusTwo
  where plusTwo = n + 2

و اگه بیاریمِ‌ش تو REPL:

Prelude> :l FunctionWithWhere.hs
[1 of 1] Compiling FunctionWithWhere ...
Ok, modules loaded: FunctionWithWhere.
Prelude> printInc 1
3
Prelude>

و حالا همون تابع رو می‌نویسیم، اما این دفعه با ‏‎let‎‏:

-- FunctionWithLet.hs
module FunctionWithLet where

printInc2 n = let plusTwo = n + 2
              in print plusTwo

هر وقت ‏‎let‎‏ بعدِش ‏‎in‎‏ بیاد، میشه یک بیانیه‌ی let یا let expression. تابعِ دوم در REPL:

Prelude> :l FunctionWithLet.hs
[1 of 1] Compiling FunctionWithLet ...
Ok, modules loaded: FunctionWithLet.
Prelude> printInc2 3
5

اگه ‏‎FunctionWithLet‎‏ رو بعد از ‏‎FunctionWithWhere‎‏ بارگذاری کردین، REPL قبلی رو تخلیه کرده:

Prelude> :l FunctionWithWhere.hs
[1 of 1] Compiling FunctionWithWhere ...
Ok, modules loaded: FunctionWithWhere.
Prelude> printInc 1
3

Prelude> :l FunctionWithLet.hs
[1 of 1] Compiling FunctionWithLet ...
Ok, modules loaded: FunctionWithLet.
Prelude> printInc2 10
12
Prelude> printInc 10

<interactive>:6:1:
    Not in scope: ‘printInc’
    Perhaps you meant ‘printInc2’ (line 4)
--  ^---------------------------^
-- بوده ‘printInc2’ م. شاید منظورتون

وقتی با دستورِ ‏‎:load‎‏ فایل ‏‎FunctionWithLet.hs‎‏ رو بارگذاری کردین، GHCi هر چیزی که قبل از اون تعریف یا بارگذاری کرده بودین رو تخلیه کرد. به همین خاطر تابعِ ‏‎printInc‎‏ از گستره‌ش خارج شده بود. منظور از گستره حوزه‌ای از کده، که انقیاد ِ یک متغیر اونجا اعمال میشه.

این یکی از محدودیتهای دستور ‏‎:load‎‏ در GHCi ِه. وقتی شروع به ساخت پروژه‌های بزرگتر کنیم که لازم باشه چندتا ماژول رو در گستره داشته باشین، به جای GHCi از یک ابزار مدیریتِ پروژه به نام Stack استفاده می‌کنیم.

تمرین‌ها: کد ذهنی

وقتِ تمرینه. اول جواب بیانیه‌های زیر رو ذهنی پیدا کنید، بعد با REPL چک کنید:

۱.

let x = 5 in x

۲.

let x = 5 in x * x

۳.

let x = 5; y = 6 in x * y

۴.

let x = 3; y = 1000 in x + 3

الان این چندتا بیانیه‌ی ‏‎let‎‏ رو تو REPL تایپ کردین. حالا می‌خوایم یه فایل باز کنیم و چندتا بیانیه‌ی ‏‎let‎‏ رو با استفاده از تعاریف ‏‎where‎‏ بازنویسی کنیم. برای مقادیری که قید می‌کنین، یه اسم هم باید اختصاص بدین (که اگه بخواین میشه تک حرفی باشن). برای مثال:

-- کار می‌کنه GHCi این تو
let x = 5; y = 2 in x * y

میشه با ‏‎where‎‏ بازنویسی کرد:

-- این رو توی فایل بنویسین
mult1      = x * y
   where x = 5
         y = 6

اونطوری تساوی‌ها رو ردیف کردن، یه انتخاب سلیقه‌ایه. فقط کافیه چنین توگذاری ای رعایت بشه، لازم نیست مساوی‌ها زیر هم باشن. دقت کنین که ما یه اسم تعریف کردیم (‏‎mult1‎‏)، و با استفاده از اون اسم در REPL به مقدارش دسترسی پیدا می‌کنیم:

Prelude> :l practice.hs
[1 of 1] Compiling Main
Ok, modules loaded: Main.
*Main> mult1
30

توجه کنید که prompt از ‏‎Prelude‎‏ به ‏‎*Main‎‏ تغییر می‌کنه، این نشون میده یه ماژول به اسم ‏‎Main‎‏ رو بارگذاری کردین.

با ‏‎where‎‏ بازنویسی کنید:

۱.

let x = 3; y = 1000 in x * 3 + y

۲.

let y = 10; x = 10 * 5 + y in x * 5

۳.

let x = 7
    y = negate x
    z = y * 10
in z / x + y

اسم فایلی که انتخاب می‌کنین مهم نیست، فقط باید پسوندِ ‏‎.hs‎‏ داشته باشه.