برای درک بهتر انواع زبانهای برنامهنویسی بهتر است کمی در تاریخچه کامپیوتر به عقب برگردیم. زمانی که آلن تورینگ از خود پرسید «محاسبه کردن به چه معناست؟». در حقیقت پرسش این است که وقتی میگوییم چیزی را محاسبه کردهایم، چه کاری انجام دادهایم؟
نتیجه تحقیقات آلن تورینگ در پرینتسون آمریکا به خلق ماشین تورینگ انجامید. یک ماشین مکانیکی که با چند جز شامل: نوارهای قابل نوشتن و پاک کردن، یک Head برای نشان دادن نوار فعلی و مقادیر 0 و 1 روی این نوارها ارائه شد.
شما برای انجام محاسبات در ماشین تورینگ به آن «دستور» میدهید که نوارهایش را جابهجا کند و یا روی نوارها چیزی بنویسد تا در نهایت مقادیر روی نوارها نتیجه مورد نظرتان را تولید کند. از همین رو، ماشین تورینگ یک ماشین «دستوری» است. فرمانهای شما میتواند به شکل «این را روی نوار فعلی بنویس؛ نوار را به چپ ببر؛ مقدار روی نوار را بخوان؛ ...» باشد. در ادامه تحقیقات معلوم شد که این ماشین، یک ماشین محاسباتی بسیار قدرتمند است که توانایی محاسبه همه الگوریتمهای محاسباتی را داراست. به عبارت دیگر، این ماشین میتواند هر محاسبهای که کامپیوتر شخصی شما قادر به حساب کردن آن است را محاسبه کند. از این رو به این ماشین، و همه مدلهای محاسباتی که در این حد قدرت دارند، Turing-complete میگویند.
نکته جالب ماجرا اینجاست که در همین زمان، آقای «آلونزو چرچ»، ریاضیدان و منطقدان دانشگاه پرینستون که از قضا استاد آلن تورینگ هم بود، روی مدل محاسباتی دیگری مبتنی بر منطق و ریاضیات با نام «محاسبات لامبدا» کار میکرد. اگرچه آلن تورینگ و آلونزو چرچ از تحقیقات یکدیگر در این زمینه اطلاعی نداشتند و به طور مستقل کارهای خود را پیش میبردند، ولی نتیجه کار آنها در مقایسه با یکدیگر به نتایج جالبی رسید.
با اینکه مدل نشانهگذاری ریاضیاتی چرچ بسیار ساده به نظر میرسید، ولی در عین حال نیز بسیار قدرتمند ظاهر شد و قابلیت «توصیف» محاسبات مختلف را دارا بود. این باعث شد که ذهن دانشمندان مختلف به این سوال معطوف شود که آیا مدل ریاضیاتی چرچ که با «ارزیابی عبارتهای ریاضی» محاسبات را انجام میدهد، قدرتی به اندازه ماشین مکانیکی تورینگ که با «اجرای دستورات» محاسبه میکند دارد؟ یا خیر؟
خروجی این تحقیق بسیار جالب بود: ماشین تورینگ و محاسبات لامبدای چرچ اگرچه دو مسیر متفاوت در محاسبات را پیش گرفته بودند، اما کاملا قدرت یکسانی داشتند. به عبارتی، هیچ الگوریتم و محاسباتی وجود ندارد که بتوانید با ماشین تورینگ انجام بدهید، ولی با محاسبات لامبدا قادر به انجام آن نباشید. از این رو، مدل محاسباتی چرچ، Turing-complete و مدل محاسباتی تورینگ، Church-complete تلقی میشود.
این دو رویکرد در محاسبات در نهایت به دو جریان از طراحی زبانهای برنامهنویسی منجر شد. 1- زبانهای برنامهنویسی «دستوری» که شما در هر لحظه به زبان دستور میدهید که چه کاری را انجام دهد، و 2- زبانهای برنامهنویسی فانکشنال در گروه زبانهای «توصیفی» که مبتنی بر محاسبات لامبدای چرچ توسعه یافت. به تعبیری، محاسبات لامبدا، مادر زبانهای برنامهنویسی تابعی است.
همانطور که پیشتر نیز اشاره شد، در زبانهای تابعی شما به «ارزیابی عبارتهای ریاضیاتی» و در زبانهای دستوری به «اجرای دستورات» میپردازید. تقریبا تمامی زبانهایی که میشناسید مانند C، C++، Go، Rust، Python، JavaScript، Ruby و Java جز زبانهای دستوری تلقی میشوند، اگرچه ممکن است در طول زمان برخی از قابلیتهای زبانهای فانکشنال و محاسبات لامبدا را اضافه کرده باشند. پارادایم برنامهنویسی «شی گرایی» نیز زیر مجموعهای از مدلهای محاسباتی دستوری است.
در مقابل زبانهایی مانند Haskell، OCaml، F#، Clojure، Scala، Lisp و Elixir زبانهای فانشکال و از دسته زبانهای توصیفی هستند. این زبانها نیز ممکن است در طول زمان برخی از جریان کارهای دستوری (مانند ST Monad ها در Haskell) را اضافه کرده باشند.
برخی از تفاوتهای این زبانها در لیست زیر آمده است. برای ملموستر بودن قضیه، این مقایسه را بین زبانهای شیگرا و فانکشنال انجام میدهیم:
از دیگر ویژگیهای زبانهای فانکشنال اعمال جزئی (Partial Apply)، اثبات بودن برای نظریه تایپها (Type Theory)، و ترکیب توابع (Composition) است.
با استفاده از قابلیت اعمال جزئی میتوانید بدون اینکه تمامی پارامترهای مورد نیاز یک تابع را به آن پاس بدهید، آن را صدا بزنید. در این صورت، خروجی شما تابعی جدیدی خواهد بود که تنها نیاز به پارامترهای پاس نشده برای تولید خروجی دارد.
در تکه کد بالا که به زبان F# نوشته شده است، stringRepeat نام تابع، و پارامترهای n و str ورودیهای تابع هستند. در خط چهارم، بدون اینکه پارامتر دوم به تابع پاس شود، خروجی در مقدار/تابع repeat3Times ذخیره شده است که خود تابعی است که با دریافت یک رشته، یک رشته دیگر را خروجی میدهد.
بدون نوشتن هیچ تایپی، کامپایلر به طور خودکار تایپ همه پارامترها را از روی نحوه استفاده آنها در بدنه تابع تشخیص داده است.
در زبانهای برنامهنویسی فانکشنال و Static-type، تایپها برای برنامه شما یک «قضیه» (Theorem) محسوب میشوند، و کدهایی که شما که در بدنه توابع مینویسید یک اثبات برای این قضایا تلقی میگردند. به دلیل نزدیکی ریاضیاتی این نظریهها، تقریباً تمامی زبانهای برنامهنویسی که برای اثبات قضایای ریاضی به کار میروند زبانهای فانکشنال هستند. این امر باعث میشود که از بسیاری از خطاها در زمان کامپایل و پیش از اجرای برنامه جلوگیری شود.
سنگبنای زبانهای فانکشنال ترکیب توابع است. شما برای نوشتن یک نرمافزار مقیاس بزرگ، صرفا کافی است توابع مختلفی تعریف کنید و سپس این توابع را با یکدیگر ترکیب (Compose) کنید.
در تکه کد بالا، تابع getFirstChar با دریافت یک رشته، کاراکتر اول آن را خروجی میدهد. همچنین تابع isLetter نیز تابعی است که چک میکند آیا کاراکتر ورودی حرف هست یا خیر؟
حال ما میتوانیم با ترکیب این دو تابع، تابع جدیدی تعریف کنیم که چک کند آیا کاراکتر اول یک رشته حرف است یا نه.
برای مطالعه بیشتر این لینک را ببینید: «چرا باید افشارپ رو یاد بگیریم و چه مزایا و کاربردهایی داره»