پرش به محتویات

Maybe

هرچه بیشتر با Elm کار کنید، نوع داده Maybe را بیشتر خواهید دید. این نوع داده، به صورت زیر تعریف شده است:

type Maybe a
  = Nothing
  | Just a

-- Nothing   : Maybe a
-- Just 3.14 : Maybe Float
-- Just "hi" : Maybe String
-- Just True : Maybe Bool

این نوع داده، دو حالت دارد. مقدار آن می‌تواند Nothing یا یک مقدار Just باشد. متغیر نوع داده این امکان را می‌دهد که یک Maybe Float و Maybe String بسته به مقدار خاص داشته باشید.

این قابلیت می‌تواند در دو سناریوی اصلی مفید باشد: فراخوانی جزیی توابع و فیلدهای اختیاری.

فراخوانی جزیی توابع

گاهی اوقات به یک تابع نیاز دارید که برای برخی ورودی‌ها پاسخ دهد، اما برای برخی دیگر نه. بسیاری از توسعه‌دهندگان با تابع String.toFloat هنگام تلاش برای تبدیل ورودی کاربر به اعداد با این حالت مواجه می‌شوند. بیایید آن را در عمل ببینیم:

> String.toFloat
<function> : String -> Maybe Float

> String.toFloat "3.1415"
Just 3.1415 : Maybe Float

> String.toFloat "abc"
Nothing : Maybe Float

سعی کنید تابع String.toFloat را با رشته‌های دیگر فراخوانی کنید تا ببینید چه اتفاقی می‌افتد.

همه رشته‌ها به عنوان اعداد منطقی نیستند، بنابراین این تابع به وضوح این موضوع را مدل‌سازی می‌کند. آیا می‌توان یک رشته را به یک عدد اعشاری تبدیل کرد؟ شاید! از آنجا می‌توانیم بر روی خروجی، از تکنیک تطبیق الگو استفاده کرده و ادامه دهیم.

تمرین

یک برنامه کوچک تبدیل واحد نوشتم که سلسیوس را به فارنهایت تبدیل می‌کند. سعی کنید تابع view را به روش‌های مختلف بازنویسی کنید. آیا می‌توانید حاشیه ورودی نامعتبر را قرمز کنید؟ آیا می‌توانید تبدیل‌های بیشتری اضافه کنید؟ برای نمونه، فارنهایت به سلسیوس یا اینچ به متر.

فیلدهای اختیاری

جای دیگری که معمولا Maybe را می‌بینید، در رکوردهایی با فیلدهای اختیاری است.

برای نمونه، فرض کنید یک وبسایت شبکه اجتماعی داریم. ارتباط مردم، دوستی و غیره. می‌دانید چه می‌گویم. وبسایت The Onion اهداف واقعی آن را در سال ۲۰۱۱ بیان کرد: جمع‌آوری هر چه بیشتر داده‌ها برای سازمان‌های اطلاعاتی. اگر می‌خواهیم تمام داده‌ها را داشته باشیم، باید مردم را به آرامی به آن عادت دهیم. بگذارید بعدا آن را اضافه کنند. ویژگی‌هایی اضافه کنید که آن‌ها را تشویق کند تا به مرور زمان اطلاعات بیشتری را به اشتراک بگذارند.

پس بیایید با یک مدل‌سازی ساده از کاربر شروع کنیم. کاربر باید یک نام داشته باشد، اما درج سن اختیاری است:

type alias User =
  { name : String
  , age : Maybe Int
  }

اکنون فرض کنید Sue یک حساب کاربری ایجاد می‌کند، اما تصمیم می‌گیرد تاریخ تولدش را ارایه ندهد:

sue : User
sue =
  { name = "Sue", age = Nothing }

دوستان Sue نمی‌توانند تولدش را تبریک بگویند. آیا آن‌ها واقعا به او اهمیت می‌دهند؟ در ادامه، Tom یک پروفایل ایجاد می‌کند و سن خود را ارایه می‌دهد:

tom : User
tom =
  { name = "Tom", age = Just 24 }

عالی است، این برای تبریک روز تولدش خوب است. اما مهم‌تر از آن، Tom بخشی از یک جامعه آماری ارزشمند است! تبلیغ‌کنندگان خوشحال خواهند شد.

خوب، حالا که چند کاربر داریم، چگونه می‌توانیم بدون نقض قوانین، نوشیدنی الکلی برای آن‌ها تبلیغ کنیم؟ کاربران احتمالا عصبانی خواهند شد اگر ما به افراد زیر ۲۱ سال تبلیغ کنیم، پس بیایید آن را بررسی کنیم:

canBuyAlcohol : User -> Bool
canBuyAlcohol user =
  case user.age of
    Nothing ->
      False

    Just age ->
      age >= 21

توجه داشته باشید که نوع داده Maybe ما را مجبور می‌کند که برای سن کاربر از تکنیک تطبیق الگو استفاده کنیم. در واقع، نوشتن کدی که فراموش کند کاربران ممکن است سن نداشته باشند، غیرممکن است. Elm این موضوع را تضمین می‌کند! حالا می‌توانیم با اطمینان نوشیدنی الکلی را تبلیغ کنیم بدون اینکه بطور مستقیم بر روی افراد زیر سن قانونی تاثیرگذار باشیم! فقط برای افرادی که به سن قانونی رسیده‌اند.

اجتناب از استفاده بیش از حد

نوع داده Maybe بسیار مفید است، اما محدودیت‌هایی دارد. برنامه‌نویسان مبتدی، بر اثر هیجان‌زدگی ممکن است از Maybe در هر جایی استفاده کنند، حتی اگر یک نوع داده سفارشی مناسب‌تر باشد.

برای نمونه، فرض کنید یک برنامه ورزشی داریم که در آن با دوستان خود رقابت می‌کنیم. با یک لیست از نام‌های دوستان شروع می‌کنید، اما می‌توانید اطلاعات بیشتری درباره تناسب اندام آن‌ها را بعدا اضافه کنید. ممکن است وسوسه شوید که آن را به این صورت مدل‌سازی کنید:

type alias Friend =
  { name : String
  , age : Maybe Int
  , height : Maybe Float
  , weight : Maybe Float
  }

تمام اطلاعات در آن وجود دارد، اما واقعا نحوه کارکرد دقیق برنامه را مدل‌سازی نمی‌کنید. مدل‌سازی آن به این صورت دقیق‌تر خواهد بود:

type Friend
  = Less String
  | More String Info

type alias Info =
  { age : Int
  , height : Float
  , weight : Float
  }

این مدل جدید اطلاعات بیشتری درباره برنامه را شامل می‌شود. فقط دو حالت واقعی وجود دارد. یا فقط نام را دارید، یا نام و مقدار زیادی اطلاعات. در تابع view، فقط به این فکر می‌کنید که آیا حالت Less را نمایش می‌دهید یا More. در این وضعیت، نیازی به پاسخ به سوالاتی مانند "اگر من یک age داشته باشم اما weight نداشته باشم، چه؟" ندارید! این حالت با مدل‌سازی فعلی غیر ممکن است!

نکته این است که اگر در مدل‌سازی خود، از Maybe زیاد استفاده می‌کنید، ارزش دارد که تعاریف type و type alias را بررسی کنید تا ببینید آیا می‌توانید مدل‌سازی دقیق‌تری داشته باشید یا نه. این کار، معمولا منجر به بازنویسی‌های خوبی در توابع update و view می‌شود!

تونی هور، مخترع null

من آن را اشتباه میلیارد دلاری خود می‌نامم. منظورم اختراع ارجاع null در سال ۱۹۶۵ بود. در آن زمان، من در حال طراحی اولین سیستم نوع داده جامع برای ارجاعات در یک زبان شی‌گرا (ALGOL W) بودم. هدف این بود که اطمینان حاصل کنم استفاده از ارجاعات، با بررسی‌هایی که بطور خودکار توسط کامپایلر انجام می‌شود، باید بطور مطلق ایمن باشد. اما نتوانستم مقابل وسوسه استفاده از یک ارجاع null مقاومت کنم، فقط به این دلیل که پیاده‌سازی آن بسیار آسان بود. این کار، منجر به خطاها، آسیب‌پذیری‌ها و خرابی‌های بی‌شماری شده است که احتمالا میلیاردها دلار درد و خسارت در چهل سال گذشته ایجاد کرده است.

پاورقی

این طراحی باعث می‌شود که شکست ضمنی باشد. هر بار که فکر می‌کنید یک String دارید، ممکن است در واقع یک null داشته باشید. آیا باید بررسی کنید؟ آیا شخصی که به شما مقدار را داده است، بررسی کرده است؟ شاید همه چیز خوب باشد؟ شاید سرور شما خراب شود؟ بعدا متوجه خواهیم شد!

Elm با عدم وجود ارجاعات null از این مشکلات اجتناب می‌کند. بجای آن از نوع داده سفارشی مانند Maybe استفاده می‌کنیم تا شکست صریح باشد. به این ترتیب هرگز غافلگیری وجود ندارد. یک String همیشه یک String است، و وقتی یک Maybe String را می‌بینید، کامپایلر اطمینان حاصل می‌کند که هر دو حالت در نظر گرفته شده‌اند. به این ترتیب شما همان انعطاف‌پذیری را دارید، اما بدون خرابی‌های ناگهانی.