ID: 8-programming-languages-in-4-years
1724472042

8 زبان برنامه نویسی در 4 سال

مقدمه

سلام! دیگه دارم به پایان تحصیل در مفطع گارشناسی رشته مهندسی کامپیوتر نزدیک می‌شم.

خب شروع تحصیل من در دانشگاه تا تقریبا 2 سال بعد مصادف شده بود با دوران بیماری کرونا که باعث شد تا همه درس‌ها و حتی امتحانات هم مجازی برگزار بشه. این یعنی لازم نبود وقتمو توی رفت و آمد تلف کنم و کلی وقت آزاد داشتم. دغدغه اولم اکثر اوقات برنامه نویسی بوده؛ که البته باعث شد خیلی جاها از درس بزنم که باعث بعضی مشکلات تحصیلی شد ...

قبل از دانشگاه

قبل از دانشگاه به لطف کلاس برنامه نویسی Java که یک ترم شرکت کرده بودم، مفاهیم ساده برنامه نویسی مثل تابع و متغیر و ... رو یادگرفتم. بعد از اون هم به صورت خودآموز هرجا دیدید کسی می‌گه خودآموز بزنید تو دهنش چون هرچیزی که ازش برای یادگیری استفاده کرده کسی زحمتش رو کشیده. به نظرم کلا اصطلاح اشتباهیه. . یکمی HTML و CSS سند و بعد از مدتی Javascript و Python سند رو یادگرفتم سند دوره های آقای امیرحسن عظیمی توی سایت Youtube که کانالی با اسم ParsClick یا پارس کلیک داره و به زبان فارسی بود، خیلی کمکم کرد. بسیار از ایشون تشکر می‌کنم . از دنیای Javascript یکمی VueJs و React هم بلد بودم سند و کمی با ExpressJs کار کرده بودم سند به لطف خدا عمرم رو خیلی با دیدن سریال های پوچ و بازی‌های گامپیوتری مسخره تلف نکردم .

از زبان Javascript بخش Promise اش رو دوست دارم و از زبان Python بخش خوانایی اش رو. از زبان Javascript قسمت وجود آموزش‌های بسیار که نادقیق و کلی هستن بدم میاد و از زبان Python قسمت کتابخونه‌های متعدد پر از مشکل و framework هایی تفکر از پایه غلطی مثل Django که باعث کلی مشکل می‌شه البته بعضی کتابخونه ها که شرکت های بزرگ پشتش هستن مثل Google در واقع از کیفیت خوبی برخورداره

موقعی داشتم از Python ناامید می‌شدم که Zen of Python رو در حال نقض شدن توی تقریبا تمام پروژه ها و حتی خود زبان دیدم. زبان Python قرار بود ساده باشه ولی هرچی جلوتر می‌رفتم با مفاهیم نه چندان ساده بلکه گیج کننده مواجه می‌شدم . کافیه فقط یکبار با async توی Javascript کار کنید تا به چیزی که توی Python پیاده سازی شده حتی نگاه هم نکنید. از اونور هم درسته، یعنی کافیه یکبار ورژن Python تون رو آپدیت کنید مثلا از 3.7 به 3.8 تا متوجه بشید برنامه نویس‌های Python چقدر بی‌مسئولیت هستن که نصف کتابخونه هایی که باهاش کار می‌کردید دیگه از کار میقته توی این مطلب در مورد قسمت minor بخونید و در عین حال مشکلات جدید رو درست نمی‌کنن؛ اون موقع قدر پایداری Javascript رو می‌دونید.

سال اول

عرضم به خدمتتون که سال اول دانشگاه در درس های مبانی زبان های نویسی و برنامه ننویسی پسرفته اسم درست درس "برنامه نویسی پیشرفته" هست که بخاطر اینکه به هیچ عنوان جدی گرفته نشد و استاد بی‌سوادی هم داشت، به این صورت نوشتم ، به ما C و C++ درس دادن

C

راستش چیز خاصی یادنگرفتم، صرفا فهمیدم که استفاده از Python در مقابل C آدم رو به دوران انسان های اولیه می‌بره:

  • توی اون فضا حتی استفاده از کتابخونه ها هم داستان داشت
    1. اولا خیلی از کتابخونه ها و تابع هایی که تقریبا همیشه توی کد هام استفاده می‌کردم مثل split و replace توشون وجود نداشت، این یعنی یا باید دستی از اول دوباره می‌نوشتمشون یا باید از کد های بقیه که در قالب کتابخونه در اومده بود استفاده می‌کردم
    2. Package Manager یا نرم افزار مدیریت کتابخونه های دانلودی به صورت پیشفرش توش وجود نداشت و حتی با وجود دانلود کردن و اضافه کردن دستی کتابخونه، بعضی از کتابخونه ها باید قبل از استفاده به اصطلاح build می‌شدن و حتی بعدش هم موقع compile برنامه خود باید ی سری دستور اضافه وارد می‌کردیم
  • اگر با Javascript کار کرده باشید احتمالا می‌دونید که حتی cross reference بین کد هاتون می‌تونید داشته باشید و مثل زبون های دیگه با مشکل وابستگی دایره‌ای Circular dependency مواجه نمی‌شید (برعکس پایتون). اما توی C برای اینکه یک فایل بیشتر از 1 بار include نشه! مجبورید چند خط macro بنویسید، که در نوبه خودش عجیبه:

    
              #ifndef MY_MODULE_1
              #define MY_MODULE_1
              ...     // your code
              #endif
            
  • compiler زبان C حتی قابلیت مقدار دهی متغیر های عمومی خارج از تابع هارو نداشت، چیزی که توی خیلی از زبان ها یک چیز رایج هست:
    
                int a = 0;
                int b = a + 1; // <-- Error: expression must have a constant value
                
                int main() { }
              
  • و اما بد تر از همه، خیلی از اوقات برنامه ای که می‌توشتم، مشکلی داشت و ی دفعه برای چند لحظه freeze می‌شد و بعد هم ارور زیبای core dumped یا segmentation fault می‌داد ( البته بعضی از اوقات هم هیچی نمی‌نویسه و از exit code برنامه خودتون باید متوجه بشید )

C++

ترم بعد بهمون C++ یاد دادن. همونطور که احتمالا می‌دونید پسوند ++ در زبان C به معنی اضافه کردن هست، و خب همونطور که از اسمش معلومه، زبان C++ باید یک نسخه بهتر و کامل تر C باشه.

قابلیت هایی بهش اضافه شده بودن، مثل:

  • namespace
  • function overload
  • کتابخونه های پیشفرض غنی تر
  • مدل مدیریت خطا
  • برنامه نویسی شیء گرا

که مفید بودن ولی در عین حال مشکلات خودشون رو داشتن و هیچکدوم از مشکلاتی که باهاش داشتم غیر از مورد expression must have a constant value رفع نشده بودن:

  • function overload اضافه شده بود ولی استفاده از template دردسر های مخصوص خودش رو داشت
  • کتابخونه های پیشفرض غنی تر شده بود ولی هنوز خیلی از توابع مثل split رو نداشت و عملا باز هم برای کارهای ساده شما به کتابخونه های دیگه احتیاج داشتید
  • مدل مدیریت خطا با همون try, catch, throw اضافه شده بود ولی هنوز مشکل core dumped وجود داشت و هیچ اطلاعی از از اینکه مشکل توی کدوم قسمت برنامه افتاده به شما نمی‌داد
  • برنامه نویسی شیء گرا اضافه شده بود اما غیر اینکه syntax جالبی نداشت، در واقع خیلی فهم کد رو سخت می‌کرد، به حدی که استفاده از همون struct رو ترجیح می‌دادم. شاید دلیلی داشته که سازنده های زبان برنامه نویسی Go شیء گرایی رو اضافه نکردن...

    برنامه نویسی شیء گرا کلی مشکلات داره که برای سامان دادنشون کلی design pattern الگوی طراحی و قاعده واسش تعریف شده. قاعده‌هایی که توی درستی‌شون شک‌وشبه وجود داره SOLID is not solid و الگوهایی هم که توش استفاده می‌شه بیشتر گیج کننده است Object-Oriented Programming is Embarrassing: 4 Short Examples 🎥

  • و در نهایت با اضافه شدن کلی قابلیت جدید توی ورژن های جدید که خیلیاشون باهم همپوشانی دارن، کار رو برای برنامه نویساش سخت‌تر و سخت‌تر می‌کنه. How C++ took a turn for the worse اگر شما برنامه نویس C++ باشید شرط می‌بندم از خیلی از قابلیت هاش حتی اطلاع هم ندارید در مورد async که جدیدا معرفی شده چیزی می‌دونی؟ و the TRUTH about C++ (is it worth your time?)

    من نمی‌گم که C++ نسبت به C هیچ پیشرفتی نداشته، البته آقای Linus Torvalds می‌گه C++ can’t solve the problem of the C language at all, it will only make things worse. This is a really bad language حتما نسبت به نسخه‌های دیگه ای از C مثل Golden C برتری قابل توجهی داشته که محبوبیت پیدا کرده؛ ولی به‌نظرم این همه مشکل Why C++ sometimes sucks (17 reasons) دیگه توی سال 2024 قابل توجیه و دفاع نیست.

سال دوم

Nim

خب ترم اول ی پروژه ماشین‌حساب اعداد بزرگ اعداد خارج از محدوده int و float بود که من برنامه رو به این نحو نوشتم که میاد هر عدد رو توی یک آرایه 5000 تایی ذخیره می‌کرد و با اینکه اصلا به صورت بهینه نوشته نشده بود توی گیتهاب نسخه بجای آرایه 5000 تایی از Vector استفاده کردم، ولی بهم گیر داده بودن که حتما آرایه باشه موقع صدا زدن توابع، کل عدد رو که یک آرایه بود، کپی می‌کرد به این کار می‌گن pass by value در مقابل pass by reference و توی بعضی از توابع مثل ضرت با تقسیم این کار رو به دفعات متعدد انجام می‌داد؛ اما با این حال به شدت سریع بود و توی چشم بهم زدنی جواب رو محاسبه می‌کرد. من که قبلا با Python کار کرده بودم، می‌دونستم چنین سرعتی اونجا محاله .

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

با خودم گفتم ای‌کاش زبانی وجود داشت که هردو تای این هارو باهم داشته باشه . خب بخاطر همین توی Google به انگلیسی عبارت جایگزین های زبان C C programming language alternatives رو جستجو کردم. چند تا زبان برنامه نویسی اومد:

  • C++: اینو که قبلا تست کرده بودم و ازش بدم میومد
  • D: این هم انگار چیز جدی‌ای نیست و برنامه نویساش بیشتر به صورت تفریحی روش کار می‌کنن و خیلی وقته که وضعیتش همینه و پیشرفتی نداشته
  • Rust: تازگی ها خیلی بهش توجه می‌شه، تصمیم گرفتم تستش کنم ببینم چطوریه. syntax نوشتار اش شبیه C++ بود ولی یکم تمیز تر. یکم مستنداتش رو خوندم و سریع رفتم سراغ برنامه نوشتن. خواستم همون نسخه Hello World رو تغییر بدم به صورتی که فقط قسمت World رو به صورت ورودی به تابع بدم؛ ولی خب به نظر می‌رسید که برای کار با نوع های داده‌ای غیر ساده غیر از int و float و bool باید با مفهوم مالکیت ownership و قرض و ... آشنا باشی که خب من راستش خیلی حوصله ام نرسید بیشتر از این ادامه بدم 😂 و رفتم سراغ بعدی
  • Nim: وارد سایتش شدم، بالای بالا نوشته بود: سریع، خوانا، ظریف Efficient, Expressive, Elegant. پایین‌تر چند تا نمونه کد گذاشته بود که باهاش بیشتر آشنا بشم. syntax اش شبیه Python بود، شاید حتی زیبا تر. خب ظاهرش به دلم نشست، تصمیم گرفتم این یکی رو هم تست کنم

توی سایت خودش 3 صفحه آموزش Tutorial داشت که همه‌اش رو خوندم. تصمیم گرفتم یکی از برنامه‌هایی که قبلا توی پایتون نوشته بودم رو دوباره اینجا بنویسم تا تفاوت رو احساس کنم. نوشتم و چند وقت بعد به صورت کتابخونه منتشرش کردم .

نکته جالب این بود که غیر از اینکه کد تمیز تر و خواناتر شد، به نسبت زیادی سریعتر هم شد. بله یکی از دلایلش اینه که زبان Nim به زبان C تبدیل و بعد compile می‌شه اما دلیل دیگه‌اش هم این بود که روش‌هایی که اینجا مثلا برای parse کردن یک عبارت استفاده می‌شه، صرفا شامل چند تا چک ساده است؛ بر خلاف زبون‌هایی مثل Javascript و Python که کار‌های سنگینی مثل RegEx Regular Expressions یا عبارات با قاعده مکررا استفاده می‌شه. این مورد توجیه می‌کنه چرا نتیجه گیری‌هایی که می‌گه این زبان‌ها فقط 4 برابر کندتر هستن، چقدر مسخره است

در ادامه خیلی از این زبان خوشم اومده بود، تقریبا همه کدهای شخصیم رو به این زبون می‌نوشتم. فضای خیلی جالبی داشت، چون به اندازه زبان‌های دیگه کتابخونه نداشت، مجبور بودم برم سر و ته‌اش رو دربیاریم و اون تیکه کد رو خودم بنویسم. مثلا کد تبدیل تاریخ میلادی خارجیا بهش می‌گن Gregorian رو کسی قبلا توی این زبان ننوشته بود، منم از این فرصت استفاده کردم و از یک کد زبان Kotlin به این زبان ترجمه کردم ، یا رابط پایگاه داده CouchDB . بعضی موقع ها هم کتابخونه های موجود خوب نبودن و بلد بودم، خودم بهترش رو نوشتم؛ مثل کتابخونه خوندن عبارت ریاضی یا کتابخونه پیمایش شبیه زبان‌های تابع‌گرا functional که ماشاالله تونست 70 تا ستاره ⭐ بگیره. بعضی موقع ها هم برای ی چیزی چند تا تابع می‌نوشتم و برای اینکه بقیه هم ازش استفاده کنن، عمومی اش می‌کردم؛ مثل این . سادگی توجه داشته باشید که ساده با آسون فرق می‌کنه. برای درک بهتر عنوان این سخنرانی رو توی اینترنت جستحو کنید: simple made easy و قابل فهم بودن این زبان، به من توانایی این رو داد که من برای اولین بار توی عمرم تونستم مشکلی رو حل کنم و pull request بزنم.

چیزهایی که یادگرفتم

  • برنامه نویسی ساخت یافته Structural Programming : این روش برنامه نویسی که سازنده Nim روش تاکید داره، اینطوریه که می‌گه تا جایی که ممکنه برنامه نباید exit point هایی مثل return و break در بیش از یک محل داشته باشه. یا می‌گه که هر if باید یک else وجود داشته باشه. اینطوری کدت استدلال پذیر می‌شه یعنی اگر به مشکلی برخورد کردی، حدس محل وقوع مشکل راحت تره. من این مورد رو یادگرفتم و حتی توی زبان‌های دیگه ای هم که برنامه نویسی می‌کنم، این مورد رو رعایت می‌کنم و واقعا جواب می‌ده
  • مدل کردن مسئله: توی Nim مفاهیمی مثل class وجود ندارن و بجاش object هست که خیلی شبیه به struct در زبان C و فقط field های داده‌ای داره. شاید فکر کنید که Nim اینطوری خیلی سخت می‌شه و خوبی های برنامه نویسی شیء گرا رو نداره؛ اما سخت در اشتباهید چون اولا شیء گرایی رو پشتیبانی می‌کنه ولی پیشنهاد کرده که ازش استفاده نکنید، دوما قابلیت‌های قوی‌تری داره که شما رو تقریبا از اون فضا بی‌نیاز می‌کنه.

    
                var 
                  s = "Salam"
                  i = " Donya"
    
                # ---- 1 param ----
    
                echo(s)
                echo s
                s.echo()
                s.echo
                echo: s
                echo(): s
                echo: 
                  s
    
                # ---- 2 params ----
    
                add(s, i)
                add s, i
                s.add(i)
                s.add i
                add s, i
                add s: i
                add(s): i
                s.add(): i
                
                add:
                  s
                do:
                  i
              

    قطعه کد بالا حالاتی رو توی ---- 1 param ---- کد های معادل برای صدا زدن تابع با یک ورودی و قسمت ---- 2 params ---- کد های معادل صدا زدن یک تابع با 2 ورودی رو نشون می‌ده. این یعنی شما می‌تونید زمان بیشتری رو برای فکر کردن صرف کنید تا اینکه به این فکر کنید که آیا فلان چیز Method بود یا تابع بود یا نه اصلا attribute بود؟

    زبان Nim قابلیت هایی خیلی خیلی قوی‌تز از decorator توی Python یا بقیه زبان ها داره که به شما عملا اجازه با قطعه کد زیرش هرکاری بکنید. این قابلیت ها به حدی خوب هستن که واقعا بقیه چیزها در مقابلش بچه بازی محسوب می‌شن. فعلا زبان رو پیدا نکردم که در فقط در بخش خوانایی کد بتونه به گرد پاش برسه؛ زبان Python یا Ruby نزدیک هستن ولی Nim واقعا ی چیز دیگست ...

    خب داشت یادم می‌رفت موضوع رو، سادگی و خوانایی در عین پشتیبانی نهی شده از شیء گرایی، به شما این اجازه رو می‌ده که بجای تمرکز روی مباحث بی‌اهمیت، روی مسئله‌تون وقت بگذارید. در طول مدت برنامه نویسی به این فکر کنید که سختار object ها رو چطور بچینم که به بهترین شکل بتونه مسئله رو حل کنم باهاشون؟ و چه تابع های بنویسم که بتونه با این object ها کار بکنه؟

  • کار با ساختمان داده‌های درختی : خب قبل از اینکه با macro ها آشنا بشم، صرفا فکر می‌کردم ساختمان داده درختی صرفا ی چیزیه که جاش توی دانشگاه هست و البته شاید بعضی موقع ها هم بدرد بخوره مثل Binary Search Tree که البته اونم کدش توی اینترنت هست.

    macro در زبان Nim تابعی هست که ورودی‌شون کد و خروجی‌شون هم کد هست. البته کدی که به macro تحویل داده می‌شه نه یک قطعه string بلکه یک درخت مفهومی نوشتار Abstact Syntax Tree یا AST هست که خب نوشتن تعدادی macro باعث شد که توی کار با ساختمان داده درخت مهارت خوبی پیدا کنم اگر کار با درخت توی برنامه نویسی بلد نیستی، به نظرم اصلا نمی‌دونی برنامه نویسی چیه

  • کار کردن با Thread ها : خب کار کردن با Thread توی زبون Python مثل یک جک بی‌مزه می‌مونه چون یکی از مهم ترین اهداف این کار افزایش کارایی هست، در صورتی که Thread ها در Python بخاطر وجود GIL نوبتی اجرا می‌شن؛ البته کتابخونه هایی مثل subprocess این مشکل رو ندارن ولی کار کردن باهاشون خیلی فرق داره و کلا ی چیز دیگه است. اینجاست که گفتم خود زبان Python تفکر Zen of Python رو نقض می‌کنه چون اصلا اصلا اصلا اینا ساده نیستن و حتی سخت تر از زبونای برنامه نویسی دیگه هستن، همه‌اش بخاطر تفکر پایه‌ای غلط.

    توی زبان Javascript هم نسبتا بد نیست و خودش همه چیز رو مدیریت می‌کنه. از Python توی این زمینه که خیلی بهتره ولی خب توی Nim دست شما باز تره برای مدیریت‌شون، که هرکدوم خوبی و بدی خودشون رو دارن

نکات منفی

خب هیچ چیز کاملا خوب نیست، بلکه نسبتی از خوبی و بدی هست. مثل زبان Nim یا هر چیز دیگه ای توی دنیا

  • جامعه کوچک : خب تعداد برنامه نویس‌هایی که این زبون رو می‌شناسن هم حتی خیلی نیستن، چه برسه به کسایی که ازش استفاده می‌کنن! این مورد باعث می‌شه که احتمالا کتابخونه هایی که بهش نیاز دارید و توی زبون‌های دیگه ازش استفاده می‌کنید، اینجا موجود نباشه.

    البته می‌تونید کتابخونه های زبان های C و C++ رو توی Nim استفاده کنید، ولی خب خیلی هم دلچسب نیست

  • مشکلات احتمالی : خب اون موقعی که من استفاده از Nim رو شروع کردم، نسبتا جوون بود و ممکن بود به چیزی برخورد کنید که ندونید مشکل از کد خودتون هست یا خود Nim 🤣. فقط ببینید که من تنهایی چند تا issue توی github شون باز کردم. البته همه شون Bug مشکل کامپیوتری نیستن اما از ورژن 2 به بعد خیلی پایدارتر شده

Elixir

این زبان ی جورایی بچه‌ی زبان Erlang حساب می‌شه و برای شرکت مخاربراتی Ericsson ساخته شده بود، با هدف اینکه بتونه تعداد زیادی کاربر رو بدون مشکل و بطور همزمان مدیریت کنه. از جمله قابلیت های ویژه‌ای که برای این کار داره، می‌شه به supervision tree برای مدیریت و تصمیم گیری برای Process های مختلف و مدل مدیریت Thread به روش M:N با ایجاد LWP توی کتاب‌های دانشگاهی بهش می‌گن Light Weight Process ، توی اینترنت با اسم Green Thread می‌شناسنش و عدم قابلیت تغییر داده immutability اشاره کرد. این زبان توی مدیریت Process و Thread Concurrency in Go, Pony, Erlang/Elixir, and Rust ها از همه ی سر و گردن بالاتره که شاید یکی از دلایلش استفاده از مدل ارتباطی Actor Actor Threading Model هست.

این یکی رو بیشتر بخاطر اینکه functional بود خواستم یادبگیرم. به طور خلاصه زبان‌های functional چیزی به نام متغیر وجود نداره البته متغیر داریم ولی تحت شرایط خاصی و اصلا اصلا رایج نیست . شاید بگید که خب ممکنه یک آرایه n تایی داشته باشیم، که در این صورت با هربار تغییر یکی از خونه های آرایه، کل‌اش رو copy کنیم؛ خب این قضیه ممکنه رخ نده به دلیل یک سری بهینه سازی ها، که اگر که تشخیص بده نیازی بهش نیست، اما خیلی اوقات هم رخ می‌ده و این تصمیمی هست که گرفتن؛ به جهت اینکه یکی از مشکلات اصلی توی برنامه هایی که موازی اجرا می‌شن، مشکل Race Condition 🏁 حالت مسابقه هست که بخاطر وجود حافظه مشترک اتفاق میفته.

اگر هیچ حافظه مشترکی بین برنامه‌هات نباشن، این یعنی که Race Condition ای رخ نمی‌ده. دیدید؟ کلا صورت مسئله پاک شد 😁

چیز هایی که یادگرفتم

  • تبدیل حلقه به تابع بازگشتی : یکی از عجایب زبون‌های functional البته نه همه‌شون اینه که چیزی به نام حلقه ندارن و شما مجبوری همه حلقه هات رو به صورت تابع بازگشتی بنویسی.

    خب شاید واسه تون سوال بشه که آیا این مورد باعث ارور Stack Overflow وقتی رخ می‌ده که تعداد تابع‌های تو در توی صدا زده شده بیشتر از حد مجاز باشه نمی‌شه؟ جوابش هم اره و هم نه هست؛ موقعی این اتفاق نمی‌فته که عبارت پایانی تابع‌تون تابعی باشه که به صورت تنها صدا زده شده باشه. در غیر این صورت در تکرار های بالا باعث اروری که اشاره شد، می‌شه. برای مثال دو تا تابع زیر برای محاسبه فاکتوریل رو در نظر بگیرید:

    
                def f_recur(1), do: 1
                def f_recur(n) do
                  f_recur(n - 1) * n
                end
    
                def f_tco(1, result \\ 1), do: result
                def f_tco(n, result \\ 1)  do 
                  f_tco(n - 1, n * result)
                end
              

    تابع f_recur یک تابع بازگشتی معمولی هست، چون برای محاسبه فاکتوریل n، اول باید فاکتوریل n-1 رو کامل حساب کنه که خودشم باز نیاز به محاسبه فاکتوریل n-2 داره و ... ، اما برای تابع f_tco نه. اجازه بدید نشون بدم چطوری اجرا می‌شن:

    
                f_recur(4) 
                (f_recur(3) * 4) 
                ((f_recur(2) * 3) * 4) 
                (((f_recur(1) * 2) * 3) * 4) 
                (((1 * 2) * 3) * 4) 
                ((2 * 3) * 4) 
                (6 * 4) 
                24
    
                f_tco(4) 
                f_tco(3, 4)
                f_tco(2, 12) 
                f_tco(1, 24) 
                24
              

    خب همینطور که دیدید، تابع f_tco یک روند اجرای خطی داره و compiler کد رو بهینه می‌کنه ولی در تابع f_recur این قضیه دقیقا برعکسه و برای اجرا کامپیوتر باید تا ته بره و بعد برگرده. به این بهینه سازی می‌گن Tail Call Optimization بهینه سازی صدا زدن تابع در انتها یا TCO

  • برنامه نویسی ساخت یافته : راستش زبان‌های functional خیلیاشون چیزی به نام return ندارن و نتیجه تابع شما می‌شه خروجی آخرین دستوری که اجرا شده. این موضوع به شما رو مجبور می‌کنه که به‌طور خاصی برنامه بنویسید
  • مدل های مدیریت Thread : برای درک فلسفه ساخت زبان پدر یا همون Erlang مجبور شدم برم درباره اش مطالعه کنم
  • استفاده از higher order functions : غیر از تبدیل حلقه به تابع بازگشتی، اگر که الگوی خاصی داشته باشه، می‌تونید از توابعی مثل map و reduce و ... استفاده کنید

معایب

راستش زبان Elixir همونطور که اول گفتم برای کارای بزرگ ساخته شده، و بخاطر این قضیه یکسری پیچیدگی‌ها که به نظرم لازمه توش وجود داره؛ ولی برای منی که نه دغدغه اش رو دارم و نه می‌تونم از پتانسیل هاش استفاده کنم، ی جورایی بی‌معنیه. در هر صورت خودم را آدم لایقی برای نقد نمی‌دونم، اما به چندتا که واقعا حس کردم اشاره می‌کنم:

  • نمی‌دونم چرا با اینکه این زبان functional هست ولی کتابخونه های معروفش مثل Ecto به حالت شیء گرایی نوشته شده و ساده نیست واقعا
  • syntax اش ترکیبی از Erlang و Ruby هست به علاوه سلیقه خودشون. یکمی عجیب و غریبه و بعضی موقع‌ها تکرار توش زیاده و این موضوع واقعا حس می‌شه

سال سوم

این سال زبان دو تا زبان از خانواده LISP یادگرفتم. کلمه LISP از دو تا کلمه List Processing تشکیل شده و خب همونطوری که از اسمش پیداست، نقطه قوتش توی کار با Linked List هست. خیلی جالبه که حتی syntax اش هم به صورت list هست که بهش می‌گن s-expression

این s-expression پر از پرانتزه و برای همه کاری هم لازمه. مثلا توی زبان C هم از [] و {} و () استفاده می‌کنن ولی توی LISP همه اش تقریبا () هست. برای مثال این عبارت توی رو در نظر بگیرید:


      2 * 3 + 4
    

معادل LISP اش می‌شه


      (+ (* 2 3) 4)
    

همونطور که می‌بینید، تابع‌ها درون پرانتز نوشته می‌شه و کلا یک روش برای صدا زدن تابع‌ها بیشتر وجود نداره. یعنی مثل بعضی از زبان ها نمی‌تونید تابع رو بین ورودی ها a + b یا بعد از ورودی a++ بنویسید؛ فقط حالا پیش از ورودی ها مجازه، اونم به این صورت: (fn a b c d ...)

حالا ی مثال دیگه:


      sin(11 / 9 * pi) + 3 * cos(0) + 12
    

      (+ (sin (* (/ 11  9) π)) (* 3 (cos 0)) 12)
    

کد زیر هم تابع محاسبه ریشه‌های معادله درجه 2 هست:


      (defun quadratic-solver (a b c)
        "Solve the quadratic equation ax² + bx + c = 0."
        (let ((Δ² (- (* b b) (* 4 a c))))
        (cond
          ;; Two real and distinct roots
          ((> Δ² 0)
            (list (/ (+ - b (sqrt Δ²)) (* 2 a))
                  (/ (- - b (sqrt Δ²)) (* 2 a))))
          ;; One real root
          ((= Δ² 0)
            (list (/ - b (* 2 a))))
          ;; No real roots
          (t (list "No real roots.")))))

      ;; Example usage:
      (quadratic-solver 1 -3 2)
    

Racket

راستش توی اینترنت خونده بودم که چند تا ایده‌هایی که تقریبا همه زبان‌ها دارن، برای اولین بار توی LISP پیاده سازی شده. مثل closure function تابعی که به متغیرهای جایی که ساخته شده دسترسی داره و garbage collector و meta programming با macro ها ؛ بخاطر همین گفتم ی تستش بکنم.

اول رفتم سراغ Clojure اما نصبش ارور داد و دنبال یک زبان خانواده LISP گشتم که راحت بتونم نصبش کنم؛ تا اینکه Racket رو پیدا کردم

نقاط ضعف

راستش یکمی باهاش کار کردم ولی خوشم نیومد ازش:

  • تابع هاش به صورت یکنواخت روی هردو list و Vector و string کار نمی‌کردن و این موضوع واقعا آدم رو آزار می‌داد
  • با اینکه سن زیادی داره ولی هنوز هم بالغ نشده، یعنی کتابخونه های زیادی نداره و همونایی هم که داره کامل نیستن
  • زبون خیلی پیچیده طراحی شده و حس می‌شه که قابلیت هاش باهم جور در نمیان
  • با اینکه ادعا می‌کنن که Racket یک زبان functional هست ولی نمی‌دونم این همه تابع برای تغییر مقدار متغیرها به اصطلاح mutate کردن پس چیه؟ چرا class داره؟ مگه شیء گراست؟!؟
  • جامعه کوچیکی داره ولی چون قدیمیه انتظار دیگه ای ازش میره. کسایی که توش فعالیت می‌کنن هم به نظر می‌رسه که برنامه نویس‌های بروز و باسوادی نیستن

Common Lisp

کتاب Land of lisp رو از اینترنت دانلود کردم. اگر اشتباه نکنم واسه سال 2007 بود. کتاب زبان LISP رو تکنولوژی آدم فضایی‌ها Alien Technology معرفی می‌کرد 😮. انصافا کتاب خوبی بود، همه چیز رو با ecosystem زبان LISP یاد می‌داد. مثلا برای نرم‌افزار کدنویسی گفته بود که یکی از ورژن‌های آماده Emacs که اسمش Portacle بود رو نصب کنید Emacs خودشم با یکی از زبان‌های خانواده LISP نوشته شده . روی Portacle به صورت پیشرفض ی سری افزونه ها مثل Expand Region واقعا یکی از بهترین افزونه‌هایی هست که توی عمرم دیدم، روی VS Code هم نصبش کردم بود که کار با این پرانتز های تو در تو رو خیلی ساده می‌کرد. یا مثل کار با Sly رو یاد داد که یکی از REPL قابلیت Read Eval Print Loop همون جایی که کد رو وارد می‌کنن و سریع جواب رو می‌گیرن. زبون‌هایی که interpreter دارن تقریبا همه شون REPL هم دارن مثل Python و Javascript های خفن زبان Common LISP هست.

مزایا

  • Loop macro : باحال‌ترین چیزی که توی LISP دیدم macro ای به‌نام Loop بود. نکته جالب‌اش اینه که انگار داری انگلیسی تایپ می‌کنی و به برنامه نویسی خیلی شبیه نیست. مثلا حلقه زیر توی لیستی به نام *list_of_number* پیمایش می‌کنه، تعداد زوج هارو می‌ریزه توی even تعداد فرد هارو می‌ریزه توی odd، جمعشون رو می‌ریزه توی total بیشترین رو نوی max و کمترین رو توی min، در نهایت به صورت یک لیست خروجی می‌ده همه شون رو:

    
                (loop for i in *list_of_number*
                  counting (evenp i) into evens
                  counting (oddp i) into odds
                  summing i into total
                  maximizing i into max
                  minimizing i into min
                  finally (return (list min max total evens odds)))
              
  • ابزار هایی که برای کار با linked list داشت خیلی خوب بود، مثل تابع‌های car و cdr و cadr و cddr و... البته انتظار هم می‌ره که اینطوری باشه چون اسم زبون هم همینو می‌گه

معایب

  • راستش درسته که ایده‌هایی که مطرح کرده اون زمان خیلی محشر بوده، ولی همین بهترین ایده‌هاش رو خیلی از زبان‌های برنامه نویسی دیگه هم دارن. مثل همین بهترین قابلیت LISP که macro هاش بودن. همین macro ها مشابه یا حتی بهترش رو توی زبان Elixir و Nim دیدم. یعنی نقاط قوت LISP واقعا دیگه نقاط قوت حساب نمی‌شن
  • خیلی از کارهای توی زبان‌های دیگه مثل Javascript و Python مثل کار با پایگاه داده یا ساخت یک web server خیلی ساده تر هست. عملا با وجود رقبا، سادگی دیگه نقطه قوت زبان LISP محسوب نمی‌شه Why Turtl Switched From CL to Js
  • زبان Common LISP برای هدفی که براش خلق شده کار با Linked list عالیه ولی مشکل از اونجایی شروع می‌شه که خواستن class و شیء گرایی رو با CLOS Common Lisp Object System به زور به زبون اضافه کنن که فقط زبون رو پیچیده کرد.
  • زبان رها شده و می‌شه گفت تقریبا مرده ☠️ فقط زبان Common Lisp رو گفتم نه کل خانواده Lisp . تقریبا همه آموزش ها واسه دوران اوج LISP هستن و چیز جدیدی در دسترس نیست

سال چهارم

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

احساس می‌شه که توی این زبان ها نوع نگاه به مسئله به صورت کلی‌نگری هست که با توجه به شرایط، این موضوع می‌تونه خوب یا بد باشه

نکته جالب دیگه در مورد این زبان‌ها اینه که تقریبا همه تابع‌ها و مفاهیم اصلی با یک کاراکتر نمایش داده می‌شه. مثلا برای جذر گرفتن، بجای استفاده از تابع هایی مثل sqrt توی زبان‌های دیگه مثل Python، از خود کاراکترش استفاده می‌کنن

Uiua

نحوه آشنایی من با این زبون و ایجاد اشتیاق 🤩 برای یادگیریش، بعد از خوندن Exploring Uiua به‌وجود اومد

این زبان به نوع خودش خیلی چیزای غیر معمول و عجیب 🔮 زیاد داشت:

  • tacit programming : ی نوع روش برنامه نویسیه که متغیر توش نداری و مستقیم با stack سر و کله می‌زنی.

    نحوه اجرای کد توی این زبان از بالا به پایین ⬇ و از راست به چپ ⬅ هست. برای مثل اگر بخواید 2 تا عدد رو با هم جمع کنید، اول باید بندازیدش توی stack و بعد تابع + رو اجرا کنید که دو تا مقدار از stack بر می‌داره و بعد جوابش رو محاسبه می‌کنه و می‌ذاره تو stack دوباره.

    
              + 1 2
              # 3
            

    با دانشی که توی طول یک هفته بدست اوردم این تابع رو برای محاسبه ریشه‌های معادله درجه 2 نوشتم. تابع با گرفتن ضرایب a و b و c ریشه‌هارو توی یک آرایه دوتایی خروجی می‌ده. البته حالت بدون ریشه رو تشحیص نمی‌ده

    
              ² ← ×.    # square
    
              Δ ← ( # b² - 4(a)(c)
                ⊃(∘|⋅⋅∘|²⋅∘)
                -××4
              )
    
              ⊍ ← ( # ax² + bx + c = 0  -->  x₁₂ = -b±√Δ/2a, returns [x₁ x₂]
                ⊃(√Δ|¯⋅∘|×2∘) # √Δ -b 2a
                ⊂⊃(+|-)
                ÷:
              )
    
              ⊍ 1 5 6    # 1x² + 5x + 6
              ## [¯2 ¯3]
            

    می‌دونم چیز عجیب غریبیه، سعی می‌کنم یکم اش رو توضیح بدم، برای فهم بقیه باید برید و خودتون یادبگیرید ولی ساده است. از تابع توان 2 یا ² شروع می‌کنم:

    
              ² ← ×.
            

    یادتون باشه که گفتم از راست به چپ باید بخونید. اولین عبارت، یک نقطه . هست، یعنی از آخرین چیزی که توی stack هست ی copy بگیر. تابع × هم که یعنی دو تا عدد از stack بردارد و باهم ضرب کن. پس × . 3 در حقیقت یعنی × 3 3 که می‌شه 9. به همین سادگی تابع مربع یا همون توان 2 رو تعریف کردیم. یعنی از الان با نوشتن ² 6 می‌تونیم توان دوم 6 رو حساب کنیم که میشه 36

    تابع بعدی تابع محاسبه دلتا Δ هست که فرمولش اینه:

    \Delta = b^2 - 4ac

    کدش می‌شه:

    
              Δ ← (
                ⊃(∘|⋅⋅∘|²⋅∘)
                -××4
              )
            

    توجه کنید که ورودی به تابع به صورت a b c داده می‌شه. پس stack تابع قبل اجرای چیزی به صورت زیر هست:

    1. a
    2. b
    3. c

    عبارت ⊃(∘|⋅⋅∘|²⋅∘) ترتیب مقدار ها توی stack رو تغییر می‌ده. یعنی اولین چیزی که توی stack هست. عبارت یعنی بنداز دور. پس ⋅⋅∘ یعنی اولی و دومی رو بنداز دور و سومی رو بچسب. ²⋅∘ هم یعنی که اولی رو بنداز دور و دومی رو به توان 2 برسون

    خب پس الان چیزایی که توی stack تابع قرار داره به ترتیب اینا هستن:

    1. a
    2. c
    3. b^2

    میرسیم به -××4 که یعنی اول 4 رو بنداز توی stack.

    1. 4
    2. a
    3. c
    4. b^2

    بعد دوتای بالایی رو ضرب کن: ×

    1. 4a
    2. c
    3. b^2

    دوباره دوتای بالایی رو ضرب کن: ×

    1. 4ac
    2. b^2

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

    1. b^2 - 4ac

    بفرما! جواب شما آماده است! 🥳

  • array based : بالاتر توضیح دادم
  • functional یعنی چیزایی که داری قابلیت تغییر جزئی ندارن. اگر مثلا آخرین چیز توی stack ات یک آرایه باشه، و بخوای چند تا از خونه هاش رو پاک کنی، باید قبلی رو از بین بره و ی جدید ساخته بشه. این موضوع به خودی خود بد نیست ولی ممکنه توی سرعت تاثیر بدی بزاره

مزایا

  • واقعا توضیحات و آموزش خوبی داشت؛ آخر بعضی از آموزش‌هاش هم تمرین با جواب گذاشته بود. البته با دوره 9 قسمته Uiua برای مبتدیان شروع کردم و راه انداخت منو
  • به نظرم کاراکتر های مناسبی برای هر کدوم از قابلیت‌ها انتخاب شده بود و کاملا منطقی بود
  • ساده است و شاید توی کمتر از یک روز بتونید باهاش برنامه بنویسید
  • بر خلاف زبان APL یا همین BQN که بعدی هست، لازم نیست حتما کاراکتر تابع یا مفهوم مورد نظر رو بنویسید، اگر چند حرف از اسمش رو هم بنویسید، خودش می‌فهمه و با کاراکترش جایگزین می‌کنه

معایب

  • شاید گل سر سبد این زبان‌های stack based زبان‌های Post Script و Forth باشن. نظر شخصیم اینه که شاید این روش برنامه نویسی توی کارهای کوچیک ساده تر باشه ولی هرچی برنامه بزرگتر می‌شه، دغدغه شما از حل مسئله دور می‌شه و می‌ره به سمت مدیریت ترتیب stack البته شاید با تمرین و تکرار زیاد دیگه بهش فکر نکنی و آسون بشه، مثل رانندگی یا هر مهارت دیگه
  • چون خیلی تازه معرفی شده، سال 2023 ، نه community داره و خیلی چیزا بهش اضافه نشده یا پیاده سازیش آسون نیست. مثل عبارت شرطی

BQN

راستش همه اول BQN رو یادگرفتن و بعد رفتن سراغ Uiua. اما من دقیقا برعکس این کار رو کردم.

مزایا

بخاطر مشکلات Uiua به این زبان پناه اوردم. این زبانی functional هست Array Language & Library Comparisons ولی مشکلات Uiua رو نداره و صد البته پایدار تره . به نظرم این زبان به حدی ساده است که باید توی مدرسه به دانش آموز های دبیرستانی درس داد. برای نمونه دو تا تکه کد اوردم باهم ببینیم:

محاسبه مقاومت معادل سری

اگر چند تا مقاومت توی مدار پشت‌ سر هم یا به اصطلاح سری وصل شده باشن، فرمول مقاومت معادل از این رابطه بدست میاد:

R_t = R_1 + R_2 + R_3 + ...

کد معادل عبارت بالا می‌شه:

به همین راحتی! حالا این کد یعنی چی؟ خب + که یعنی همون جمع خودمون و ` هم یعنی که تابع پشتش رو بین تک تک اعضا اعمال کن.

پس یعنی اگر ما 3 مقاومت R_1 = 1 و R_2 = 2 و R_3 = 3 داشته باشیم، جواب اینطوری نوشته می‌شه


      +´ ⟨1,2,3⟩
    

که معادل کد زیر هست


      1 + 2 + 3
    
محاسبه مقاومت معادل موازی

فرمول ریاضی:

\frac{1}{R_t} = \frac{1}{R_1} + \frac{1}{R_2} + \frac{1}{R_3} + ...

کد معادل:


      +´⌾÷
    

همین؟ اره، فقط 4 تا حرف لازم بود. خب حالا بریم ببینیم چطور کار می‌کنه

اول از همه ی عملگر هست که کنارش دوتا تابع قرار می‌گیرن و عبارت G⌾F در حقیقت یعنی F⁻¹ G F. که با جایگذاری داریم:


      ÷⁻¹ +´ ÷
    

اگر تابع ÷ 2 تا ورودی داشته باشه، تقسیم رو انجام می‌ده ولی اگر فقط 1 ورودی داشته باشه، معکوس می‌کنه. یعنی مثلا ÷2 می‌شه ½. +` هم رو که قسمت قبل یادگرفتیم و فقط می‌مونه ÷⁻¹، این -1 بالای تابع یعنی معکوس خودش.

فرض کنید مقاومت‌هامون همونایی هستن که توی قسمت قبل استفاده شدن. حالا بهتون نشون می‌دم که چطور اجرا می‌شه:


      ÷⁻¹ +´ ÷ ⟨1, 2,   3⟩
      ÷⁻¹ +´   ⟨1, 0.5, 0.3333⟩
      ÷⁻¹       1.83333
      0.54545454
    

معایب

  • اینکه برای تغییر یک آرایه باید ازش کپی بگیری، یکم از لحاظ حافظه اشغالی توی RAM و سرعت می‌تونه اذیت بکنه. البته تا جایی که می‌تونید نباید از این کار ها توی زبان های functional مثل این بکنید
  • ساختمان داده hash map نداره و فقط آرایه داره. نمی‌دونم چقد نیاز می‌شه ولی به نظرم باید می‌بود چنین چیزی

بررسی

  • فکر نمی‌کنی وقت تلفی بود این همه یادگیری؟ واقعا ارزشش رو داشت؟ نه اصلا! هر سال زبان هایی یادگرفتم که تفکر و فلسفه متفاوتی نسبت به بقیه داشتن. حتی اگر از این زبان‌ها در آینده استفاده نکنم.

    این نقل‌قول رو خیلی دوست دارم و به‌نظرم درسته:

    progress is impossible without change, and those who cannot change their minds cannot change anything -- Bernard Shaw

    این نکته رو هم باید توجه کنید که اگر چیزی رو در نگاه اول متوجه نشید، دلیل نمی‌شه اون چیز پیچیده باشه foreign ≠ confusing

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

  • کدومشون بهتر بود؟ الان خودت از کدوما استفاده می‌کنی؟ راستش من از Nim خیلی خوشم اومد و از موقعی که یادش گرفتم، تقریبا همه جا ازش استفاده می‌کنم، از BQN هم تفننی برای حل مسئله استفاده می‌کنم. زبان‌های Javascript و Python هم کاربردشون زیاده و بعضی موقع‌ها نیاز می‌شه

  • چطوری تمرین می‌کردی زبون‌هایی که یادگرفتی رو؟ بستگی داره ولی حل مسئله های سایت Advent of Code واسه شروع خوبه چون خیلی به روش‌های مختلف حلش کردن و جوابش رو توی اینترنت گذاشتن.

  • باهاشون پروژه زدی؟ اره چندتایی زدم. بزرگترین‌شون سیستم مدیریت یادآوری بود که به دلیل اینکه خیلی کاربردی نبود، از کار انداختمش. ولی دمو هاش موجوده

  • به منم پیشنهاد میکنی همین کار رو بکنم؟ راستش من بیشتر از مهندسی نرم‌افزار خوشم میاد و زبان‌های مختلف برام جذابه؛ به علاوه فعلا مشکل مالی ندارم و تشکیل زندگی ندادم. شاید شما بخواید توی زمینه دیگه‌ای مثل هوش مصنوعی یا زیست‌فناوری فعالیت کنید و واسه‌تون این موراد مهم نباشه. یا اینکه شما مهارتتون کمه و ترجیح می‌دید اول جایی کار کنید یا تجربه‌ای کسب کنید و بعد اگر فرصت شد دنبالش برید.

    یاد داستانی افتادم که خیلی به این سوال مربوط می‌شه:

    روزی گربه می‌خواست از رود رد بشه، ولی می‌ترسید که عمق آب زیاد باشه و غرق بشه

    می‌دونست که شتر هر روز از اونجا رد می‌شه؛ بخاطر همین از شتر در مورد عمق آب پرسید

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

    با عصبانی برگشت پیش شتر و بهش گفت: مگه نگفتی که عمق رود تا زانو هست؟

    شتر گفت: من گفتم زانوی خودم نه زانوی تو!

    پس حتما پیشنهاد هارو با شرایط زندگی خودتون بسنجید، ممکنه بهترین پیشنهاد ها بدرد شما نخورن

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

    نکته دیگه هم اینکه فکر نکن چون تو نمی‌شناسی جایی رو که با این‌ها کار کنن، پس بازار کار نداره