Haskell برای توسعه دهندگان Elm: نام گذاری برای چیزها (قسمت 1 – Functors)
این پست برای همه آن دسته از توسعه دهندگان Elm (و به طور کلی برنامه نویسان کاربردی) است که در مورد Haskell کنجکاو هستند و مایلند بدانند چگونه آنچه قبلاً می دانند و دوست دارند از Elm به طور مستقیم به Haskell نقشه می رود.
همچنین، از آنجایی که مجموعه ویژگی ها و نحو Haskell گسترده تر از Elm است، البته باید سعی کنم شکاف ها را پر کنم و موارد خاصی را توضیح دهم که در Elm وجود ندارد. 😉
اگر این پست اول به اندازه کافی مورد توجه قرار گیرد، ممکن است در نظر داشته باشم که پست های بعدی بیشتری اضافه کنم و این را به یک سری تبدیل کنم (امیدوارم).
اجازه دهید در موردش صحبت کنیم Functor
s!
اگر قبلاً کد هسکل را دیده اید، واقعاً می توانید بگویید که به شدت بر نحو Elm تأثیر گذاشته است، یکی از بزرگترین تفاوت ها این است که در هاسکل اعلان های نوع قبل از آن ::
، در حالی که در علم فقط توسط :
.
تفاوت بزرگ دیگر این است که در Haskell مفهومی به نام وجود دارد کلاس های تایپ، که می توانیم کم و بیش توضیح دهیم و بگوییم که آنها یک کلاس از انواع را گروه بندی می کنند و به ما کمک می کنند تا رابط هایی را تعریف کنیم که یک نوع داده خاص باید انجام دهد تا “نمونه ای از آن کلاس نوع خاص” در نظر گرفته شود.
به عنوان مثال، نوع کلاس Functor در Haskell به صورت زیر تعریف می شود:
class Functor f where
fmap :: (a -> b) -> f a -> f b
این اعلان کلاس نوع در Haskell فقط به این معنی است که برای یک نوع عمومی است f
، اگر تابع نقشه داشته باشد “Functor” خواهد بود. همانطور که می بینید، هر تابع نقشه (یا fmap) باید دارای امضا باشد (a -> b) -> f a -> f b
. به طور دقیق تر، می توان گفت که هر نوع سفارشی که تابعی را با امضای این نوع پیاده سازی می کند نمونه ای از نوع کلاس Functor دارد.
احتمالاً نیازی به توضیح عملکرد (f)map نیست، زیرا شما در حال حاضر هر روز از Functors در Elm استفاده می کنید! 🤯 برای ذکر برخی از آنها: List
، Maybe
، Result
، Dict
، Set
، و غیره…
چرا به آن کلاس تایپ می گویند اپراتور بجای قابل نقشه برداری? 🤷🏼♂️
چرا این مهم است؟ خب، من فکر میکنم یکی از تصمیمهای طراحی Elm این بود که از ابتدا ساده باشد و مردم را با کلمات کلیدی آزار ندهد، فقط به آنها اجازه دهید از کد استفاده کنند و شهودی در مورد چگونگی کار کردن و انجام کارها کسب کنند. اما متأسفانه اگر بخواهیم Haskell را بعد از Elm یاد بگیریم، باید شروع کنیم به گذاشتن نام های تصادفی روی چیزها! 😉
عملگرهای Infix در واقع خوب هستند
با این حال، یکی دیگر از تفاوت های بزرگ بین Haskell و Elm آشکارا عشق هسکلرها به آن است عملگرهای infix. به عنوان مثال، به جای استفاده از fmap می توانید از آن استفاده کنید <$>
اپراتور. کد دنیوی Elm زیر را در نظر بگیرید:
[1,2,3,4] |> List.map ((*) 2) -- > [2,4,6,8] : List number
این می تواند به سبک های مختلف در Haskell نوشته شود:
fmap (*2) [1..4] -- > [2,4,6,8]
map (*2) [1..4] -- > [2,4,6,8]
(*2) <$> [1..4] -- > [2,4,6,8]
[1..4] <&> (*2) -- > [2,4,6,8]
(*2) `fmap` [1..4] -- > [2,4,6,8]
(*2) `map` [1..4] -- > [2,4,6,8]
تنها از این قطعه چیزهای زیادی می توان آموخت:
- توجه کنید که
map
فقط یک پیاده سازی خاص تر ازfmap
که در Haskell فقط با آن کار می کندList
s! - در Haskell ما مقداری قند نحوی خوب برای طیف وسیعی از چیزها داریم، جایی که
[1..4]
برابر است باList.range 1 4
در الم - کار با عملگرهای infix و توابع جزئی اعمال شده در Haskell به طور کلی ساده تر است، جایی که افزودن یک پارامتر به هر دو طرف اپراتور به معنای چیزهای کاملاً متفاوتی است اگر آن عملیات جابجایی نباشد (
2^
در مقابل^2
). - هر تابعی در Haskell را می توان به عنوان یک عملگر infix با بکتیک استفاده کرد (مانند خطوط 5 و 6 قطعه Haskell، این در ابتدا عجیب به نظر می رسد، اما گاهی اوقات در واقع به خوانایی کمک می کند!).
- اگر به نسخه برگردانده شده نیاز دارید
<$>
به دلایلی می توانید آن را از آن وارد کنیدData.Functor
و نامیده می شود<&>
🙈
چرا این خوب است؟ من بحث نمی کنم که این بهتر است یا بدتر، اما حداقل گزینه های بیشتری در زبان برای بیان مطالب داریم!
چیزی که به شما گفته نشده این است Elm در واقع تایپ کلاس دارد! 😱 توجه کرد که number
قطعه قبلی را تایپ کنید؟ comparable
نمونه دیگری از نوع کلاس استفاده شده در elm/core
بنابراین تایپکلاسها در زبان وجود دارند اما ما به عنوان کاربران الم نمیتوانیم آنها را تعریف کنیم، فقط خالق او… 😜
چرا Haskell و typeclasses درست است!
این بخش کمی مزخرف و کاملاً ذهنی است که من چگونه برنامه نویسی را می بینم، اما چند وقت پیش این را توییت کردم:
آیا مطمئن هستید که ما به کلاس های تایپ نیاز نداریم؟ 😅
11:38 – 16 سپتامبر 2022
اکنون زمان مناسبی است که خودم را با جزئیات بیشتر توضیح دهم! (و بدون تلاش برای توهین به کسی 😉).
من فکر می کنم این واقعیت که ما باید چنین کد دیگ بخاری بنویسیم یک است اشتباه، اگر Result
تابع است و Cmd
هم، ما نباید مجبور به نوشتن باشیم که تابع نقشه برداری به صورت دستی! در Haskell، شما می توانید به راحتی به این شکل دست پیدا کنید:
{-# language DeriveFunctor #-}
-- ...
newtype HttpCmd err a
= HttpCmd (Cmd (Result (Error err) a))
deriving (Functor)
با ایجاد یک نوع جدید به جای نام مستعار نوع و استفاده از جادوی خاص Haskell، اکنون میتوانیم از آن استفاده کنیم fmap
یا <$>
و هرگز نباید چنین عملکردی را با دست بنویسید!
البته شما هنوز هم می توانید آن را بنویسید fmap
برای این نوع سفارشی با دست عمل می کند، اما یکی از بهترین چیزها در مورد Haskell به نظر من این است که ارائه می دهد مکانیزم های استخراج تا چیزها را به صورت رایگان برای ما پیاده سازی کند. یکی از این مکانیسم ها است DeriveFunctor
پسوند زبان
اگر میخواستیم رویکرد مستعار نوع را حفظ کنیم، میتوانستیم همان تابع را در Haskell به این شکل بنویسیم:
httpCmdMap ::
(Functor f1, Functor f2) =>
(a -> b) ->
f1 (f2 a) ->
f1 (f2 b)
httpCmdMap f = fmap (fmap f)
این یک تفاوت دیگر با الم است: هر آنچه در سمت چپ فلش چاق می بینیم (=>
) در اعلان نوع، نامیده می شوند محدودیت های کلاس تایپ، و منظور آنها در این مورد خاص این است که برای هر نوع استفاده کنیم f1
و f2
، آنها نیاز به یک نمونه از Functor
کلاس تایپ.
همانطور که ممکن است متوجه شده باشید، Haskell دارای یک سیستم پسوند کامپایلر است که به ما امکان می دهد ویژگی های اضافی را به زبان تغییر دهیم، این دقیقاً همان چیزی است که {-# language DeriveFunctor #-}
میکند.
من نمی گویم که ما باید این را در الم داشته باشیم (در واقع، بسیاری از شکایت ها از هاسکل در این واقعیت نهفته است که ما احتمالاً پسوندهای زبان بسیار زیاد قبلا، پیش از این! 🙈) اما داشتن انواع کلاس هایی که دارای رابط های مشترک هستند و امکان استفاده مجدد از توابع و عملگرهای infix در انواع مختلف، از دیدگاه من یک برد-برد است! 🚀
یک نکته کوچک در مورد ترکیب عملکرد
من بسیاری از توسعه دهندگان Elm را دیده ام (مخصوصاً افرادی که این زبان را یاد می گیرند یا به طور کلی در برنامه نویسی عملکردی تازه کار هستند) از الگوی زیر استفاده می کنند که برای من کمی اشتباه است:
someListOfAs
|> List.map turnAtoB
|> List.map turnBtoC
|> List.map turnCtoD -- > List d
هر زمان که چیزی را چندین بار نقشه برداری می کنید، ما می توانیم از این واقعیت استفاده کنیم که همیشه می توانیم یک بار نقشه، و N توابع را بنویسید با یکدیگر!
-- please do this instead 🙏🏻
someListOfAs
|> List.map (turnAtoB >> turnBtoC >> turnCtoD)
از آنجایی که لوله کشی وسایل با |>
نان و کره نارون است، مردم واقعاً نمی ایستند و فکر می کنند که می توانند چندین عملیات را همزمان با هم ترکیب کنند، اما هی! به همین دلیل است که ما ترکیب تابع داریم و همه چیز به صورت پیشفرض در Elm انجام میشود، بنابراین اجازه میدهیم از آن استفاده کنیم! 🤓
و در اینجا یک نکته کوچک به نفع الم وجود دارد، عملگرهای انتخاب شده برای زبان هستند بسیار خواندنی تر به نظر من بیشتر از آنهایی که در هاسکل هستند:
- ترکیب چپ به راست:
>>
در الم،Control.Arrow.>>>
در هاسکل (نه در پیش درآمد! 😭). - ترکیب راست به چپ:
<<
در الم، فقط.
در Haskell که برای تازه واردان بسیار عجیب است. 😕 - برنامه کاربردی عملکرد چپ به راست:
|>
در الم (لوله محبوب)،&
در Haskell، کاملاً وحشتناک است، جای تعجب نیست که به اندازه Elm استفاده نمی شود. 🥲 - برنامه کاربردی تابع راست به چپ:
<|
در الم،$
در Haskell که احتمالاً یکی از پرکاربردترین عملگرهایی است که در هنگام یادگیری Haskell اصلاً خواندنی نیست! 🫢
سپاسگزاریها
من ایده اولیه این پست وبلاگی را در هواپیما داشتم ✈️ بازگشت به اسپانیا، اما چیزی که واقعاً مرا تشویق کرد این بود که تلاش کنم و عشقم را به هاسکل با چند مهندس بسیار خاص Elm به اشتراک بگذارم که مجبور شدند مدتی مرا تحمل کنند و من این کار را انجام دادم. دوست دارم تشکر ویژه کنم: @tomaslatal، @janiczek و @janjelinek. 🙌🏻
امیدوارم همه از این پست لذت برده باشید، یک یا دو چیز یاد گرفته باشید و شما را ترغیب کرده باشید که کمی بیشتر از Haskell یاد بگیرید. 😉
همچنین میخواهم از @serras برای تصحیح این پست تشکر کنم.
اگر از این پست لذت بردید و دوست دارید این پست را به یک سریال ببینید (من از قبل ایده هایی برای پست هایی در مورد Applicatives، Monads، IO، ترکیب کننده های تجزیه کننده و غیره در سر دارم.)، لطفا آن را در شبکه های اجتماعی خود به اشتراک بگذارید و من را دنبال کنید توییتر! 🙌🏻