خواندن نوع داده¶
در فصل مفاهیم پایه به صورت تعاملی با حال و هوای کدنویسی در Elm آشنا شدیم. در ادامه، دوباره این کار را خواهیم کرد، اما با یک سوال جدید در ذهن. نوع داده مقدار خروجی کدام است؟
نوع داده اولیه و لیست¶
بیایید چند عبارت ساده وارد کنیم و ببینیم چه اتفاقی میافتد:
مقدار 3.1415
را تایپ کنید و کلید ENTER را فشار دهید. باید 3.1415
به همراه نوع داده Float
چاپ شود.
خوب، اما دقیقا چه اتفاقی در حال وقوع است؟ هر ورودی نشاندهنده مقدار به همراه نوع داده آن است. میتوانید این مثالها را به صورت زیر بخوانید:
- مقدار
"hello"
یکString
است. - مقدار
False
یکBool
است. - مقدار
3
یکInt
است. - مقدار
3.1415
یکFloat
است.
Elm قادر است نوع داده هر مقداری که وارد میکنید را تشخیص دهد! بیایید ببینیم با لیست چه اتفاقی میافتد:
میتوانید این نوع داده را به صورت زیر بخوانید:
- یک
List
از مقادیرString
داریم. - یک
List
از مقادیرFloat
داریم.
نوع داده یک توصیف کلی از مقدار خاصی است که به آن نگاه میکنیم.
تابع¶
بیایید نوع داده یک تابع را ببینیم:
سعی کنید تابع round
یا sqrt
را وارد کنید تا نوع داده آن را ببینید.
تابع String.length
نوع داده String -> Int
دارد. به این معنی است که یک آرگومان String
میگیرد و یک مقدار Int
برمیگرداند. پس بیایید سعی کنیم یک آرگومان به آن بدهیم:
پس با یک تابع String -> Int
شروع میکنیم و یک آرگومان String
به آن میدهیم. این فراخوانی منجر به یک مقدار Int
میشود.
اما چه اتفاقی میافتد اگر یک String
ندهید؟ سعی کنید String.length [1,2,3]
یا String.length True
را وارد کنید تا ببینید چه اتفاقی میافتد.
متوجه خواهید شد که یک تابع String -> Int
باید یک آرگومان 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
است:
آیا آن 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 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