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

خواندن نوع داده

در فصل مفاهیم پایه به صورت تعاملی با حال و هوای کدنویسی در Elm آشنا شدیم. در ادامه، دوباره این کار را خواهیم کرد، اما با یک سوال جدید در ذهن. نوع داده مقدار خروجی کدام است؟

نوع داده اولیه و لیست

بیایید چند عبارت ساده وارد کنیم و ببینیم چه اتفاقی می‌افتد:

> "hello"
"hello" : String

> not True
False : Bool

> round 3.1415
3 : Int

مقدار 3.1415 را تایپ کنید و کلید ENTER را فشار دهید. باید 3.1415 به همراه نوع داده Float چاپ شود.

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

  • مقدار "hello" یک String است.
  • مقدار False یک Bool است.
  • مقدار 3 یک Int است.
  • مقدار 3.1415 یک Float است.

Elm قادر است نوع داده هر مقداری که وارد می‌کنید را تشخیص دهد! بیایید ببینیم با لیست چه اتفاقی می‌افتد:

> [ "Alice", "Bob" ]
["Alice","Bob"] : List String

> [ 1.0, 8.6, 42.1 ]
[1.0,8.6,42.1] : List Float

می‌توانید این نوع داده را به صورت زیر بخوانید:

  • یک List از مقادیر String داریم.
  • یک List از مقادیر Float داریم.

نوع داده یک توصیف کلی از مقدار خاصی است که به آن نگاه می‌کنیم.

تابع

بیایید نوع داده یک تابع را ببینیم:

> String.length
<function> : String -> Int

سعی کنید تابع round یا sqrt را وارد کنید تا نوع داده آن را ببینید.

تابع String.length نوع داده String -> Int دارد. به این معنی است که یک آرگومان String می‌گیرد و یک مقدار Int برمی‌گرداند. پس بیایید سعی کنیم یک آرگومان به آن بدهیم:

> String.length "Supercalifragilisticexpialidocious"
34 : Int

پس با یک تابع String -> Int شروع می‌کنیم و یک آرگومان String به آن می‌دهیم. این فراخوانی منجر به یک مقدار Int می‌شود.

اما چه اتفاقی می‌افتد اگر یک String ندهید؟ سعی کنید String.length [1,2,3] یا String.length True را وارد کنید تا ببینید چه اتفاقی می‌افتد.

متوجه خواهید شد که یک تابع String -> Int باید یک آرگومان String بگیرد!

یادداشت

توابعی که چندین آرگومان می‌گیرند، در نهایت دارای پیکان‌های بیشتری می‌شوند. برای نمونه، یک تابع داریم که دو آرگومان می‌گیرد:

> String.repeat
<function> : Int -> String -> String

دادن دو آرگومان مانند String.repeat 3 "ha" منجر به خروجی "hahaha" می‌شود. فکر کردن به عملگر <- به عنوان یک روش عجیب برای جدا کردن آرگومان‌ها خوب است، اما دلیل واقعی آن را در فصل ضمیمه توضیح می‌دهم. این واقعا جالب است!

نشانه‌گذاری نوع داده

تا به حال فقط اجازه داده‌ایم Elm نوع داده را تشخیص دهد، اما این امکان وجود دارد که یک نشانه‌گذاری نوع داده را در خط بالای تعریف تابع بنویسید. هنگام کدنویسی، می‌توانید چیزهایی مانند این بگویید:

half : Float -> Float
half n =
  n / 2

-- half 256 == 128
-- half "3" -- error!

hypotenuse : Float -> Float -> Float
hypotenuse a b =
  sqrt (a^2 + b^2)

-- hypotenuse 3 4  == 5
-- hypotenuse 5 12 == 13

checkPower : Int -> String
checkPower powerLevel =
  if powerLevel > 9000 then "It's over 9000!!!" else "Meh"

-- checkPower 9001 == "It's over 9000!!!"
-- checkPower True -- error!

افزودن نشانه‌گذاری نوع داده الزامی نیست، اما قطعا توصیه می‌شود! مزایای آن عبارتند از:

  • کیفیت پیام خطا — افزودن نشانه‌گذاری نوع داده، به کامپایلر می‌گوید که در حال تلاش برای انجام چه کاری هستید. پیاده‌سازی ممکن است اشتباهاتی داشته باشد و حالا کامپایلر می‌تواند آن را با قصد و نیت شما مقایسه کند. “گفتید آرگومان تابع powerLevel یک مقدار Int است، اما با یک مقدار String استفاده می‌شود!”

  • مستندسازی — وقتی بعدا به کد مراجعه می‌کنید (یا وقتی یک همکار برای اولین بار به آن مراجعه می‌کند) دیدن اینکه چه چیزی به داخل و خارج از تابع می‌رود، بدون اینکه مجبور باشید پیاده‌سازی را به دقت بخوانید، می‌تواند واقعا مفید باشد.

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

متغیر نوع داده

با مرور بیشتر کد Elm، شروع به دیدن نشانه‌گذاری‌های نوع داده با حروف کوچک در آن‌ها خواهید کرد. یک نمونه رایج تابع List.length است:

> List.length
<function> : List a -> Int

آیا آن a کوچک در نوع داده را متوجه شدید؟ به آن متغیر نوع داده می‌گویند. این متغیر می‌تواند بسته به اینکه چگونه تابع List.length استفاده می‌شود، متفاوت باشد:

> List.length [1,1,2,3,5,8]
6 : Int

> List.length ["a", "b", "c"]
3 : Int

> List.length [True, False]
2 : Int

فقط طول لیست را می‌خواهیم، بنابراین مهم نیست که چه چیزی در آن وجود دارد. متغیر نوع داده a می‌گوید که می‌توانیم هر مقداری را مطابقت دهیم. بیایید به یک مثال رایج دیگر نگاه کنیم:

> List.reverse
<function> : List a -> List a

> List.reverse ["a", "b", "c"]
["c", "b", "a"] : List String

> List.reverse [True, False]
[False, True] : List Bool

دوباره، متغیر نوع داده a می‌تواند بسته به اینکه چگونه تابع List.reverse استفاده می‌شود، متفاوت باشد. اما در این مورد، یک مقدار a در ورودی و خروجی داریم. به این معنی که اگر یک List Int بدهید، باید یک List Int نیز دریافت کنید. هنگامی که تصمیم می‌گیریم مقدار a چیست، نوع داده آن در جاهای دیگر تکرار می‌شود.

یادداشت

متغیر نوع داده باید با یک حرف کوچک شروع شود، اما می‌تواند شامل کلمات کامل باشد. می‌توانیم نوع داده تابع List.length را به صورت List value -> Int و نوع داده تابع List.reverse را به صورت List element -> List element بنویسیم. این نام‌گذاری خوب است به شرطی که با یک حرف کوچک شروع شود. متغیرهای نوع داده a و b بطور متداول در بسیاری از جاها استفاده می‌شوند، اما برخی از نشانه‌گذاری‌های نوع داده از نام‌های خاص‌تر بهره‌مند می‌شوند.

متغیر نوع داده محدود

یک نوع خاص از متغیر نوع داده در Elm وجود دارد که به آن متغیر نوع داده محدود می‌گویند. رایج‌ترین نمونه آن، نوع داده number است که تابع negate از آن استفاده می‌کند:

> negate
<function> : number -> number

سعی کنید عبارت‌هایی مانند negate 3.1415، negate (round 3.1415) و negate "hi" را امتحان کنید.

بطور معمول، متغیر نوع داده می‌تواند با هر چیزی پر شود، اما number فقط می‌تواند با مقادیر Int و Float پر شود. این کار محدودیت‌هایی برای حالت‌های ممکن ایجاد می‌کند.

فهرست کامل این متغیرها به شرح زیر است:

  • number به Int و Float اجازه می‌دهد
  • appendable به String و List a اجازه می‌دهد
  • comparable به Int، Float، Char، String و لیست/تاپِل با مقدار comparable اجازه می‌دهد
  • compappend به String و List comparable اجازه می‌دهد

این متغیرهای نوع داده محدود وجود دارند تا عملگرهایی مانند (+) و (<) انعطاف‌پذیر بیشتری داشته باشند.

تا اینجا، پوشش خوبی از نوع داده برای مقادیر و توابع داشته‌ایم، اما در صورت نیاز به ساختار داده‌های پیچیده‌تر، چه باید کرد؟


یادداشت مترجم

یکی از مهم‌ترین ابزارهای این فصل، Elm Search نام دارد. با استفاده از این ابزار می‌توانید دو کار مهم انجام دهید:

  • جستجو بر اساس نام تابع: کدام ماژول‌ تابع ‍‍map را ارایه می‌دهد؟
  • جستجو بر اساس نشانه‌گذاری نوع داده: کدام تابع ورودی رشته‌ای دریافت می‌کند و خروجی عددی می‌دهد؟ String -> Int