پیش گفتار
تو این نوشته سعی شده افراد با سطح دانش برنامه نویسی متوسط هدف قرار داده بشن، بنابراین اگه بیشتر میدونید خیلی راحت میتونید بخش هایی که بلدید رو رد کنید، و اگه جایی براتون مبهم بود، خجالت نکشید و تو کامنتا سوال بپرسید، سعی میکنم جوابتون رو هر چه سریع تر بدم، و یا اگه گسستگی مطلب در جایی دیدید بگید که اطلاعات بیشتری اضافه کنم.
اگه برنامه نویس باشید و توی Stack Overflow و آمار های سالیانش چرخ زده باشین احتمال زیاد هر سال افشارپ رو جز زبان هایی که بیشتر میزان حقوق رو دارن دیدید، ولی تا حالا براتون سوال پیش اومده که این زبان چیه؟
افشارپ (به انگلیسی: #F یا FSharp) یه زبان برنامه نویسی متن باز و کراسپلتفرم (قابل اجرا روی لینوکس و مک و ویندوز) هست که توسط موسسه تحقیقاتی مایکروسافت (Microsoft Research) ایجاد شد. این زبان، یه زبان تابعی (یا همون Functional) روی پلتفرم داتنت مایکروسافته که از پارادایم شی گرایی هم پشتیبانی میکنه. (این که زبان Functional و یا شی گرایی چیه رو جلوتر توضیح میدم).
به طور خلاصه، هر کاری! این زبان، یک زبان کاملا عام منظورست و برای هر کاری میتونید ازش استفاده کنید، همچنین برای هر کیس خاص هم کتاب خونه ها و موتور های خاصی توسعه داده شده که احساس کمبود نمیکنید.
نمونه هایی از کاربرد های افشارپ:
پارادایم های برنامه نویسی روش هایی هستن که باهاشون میتونیم زبان های مختلف رو بر اساس ویژگی هایی که دارن دسته بندی کنیم.
پارادایم Object Oriented، پارادایمی از نوع زبان های برنامه نویسی دستوریه (Imperative)، یعنی اینکه برنامه شما در هر لحظه به ماشین میگه که حالت برنامه (بخوانید متغیر ها و همه المان های برنامه) رو چطور تغییر بده و برای لحظه به لحظه اتفاقی که میفته یه دستور کار داره. در حالی که پارادایم Functional از گروه زبان های توصیفی (Declarative) هست، یعنی شما فقط به ماشین میگید که اون حالت نهایی که مد نظرتون هست چه ویژگی هایی داره، بدون اینکه خودتون محاسبش کنید.
زبان های Functional از اونجایی که به منطق و ریاضیات نزدیک تر هستن، اعتبار سنجی برنامه راحت تره، یعنی شما زمان کامپایل میتونید باگ های بیشتری رو بگیرید. در واقع میشه گفت در این زبان ها به جای اجرای برنامه (Execute)، از واژه ارزیابی برنامه (Evaluate) استفاده میشه، چون که توابع مثل توابع ریاضی فقط قراره محاسبه بشن، بدون اینکه در حین این محاسبه اتفاق بی ربطی مثل تغییر یه داده در یه گوشه از برنامه صورت بگیره. مثلا شما حین محاسبه مجموع صورت حساب یک مشتری در رستوران موشک هوا نمیکنید! و صدا زدن یک تابع با پارامتر های یکسان، همواره مقدار یکسانی رو برمیگردونه و هیچ چیزی در بیرون تغییر پیدا نمیکنه. به بیان دیگه شما عوارض-جانبی (Side-effect) ندارید. برای همین در افشارپ همه چیز به طور پیشفرض غیر قابل تغییر (Immutable) هست، و شما نمیتونید بگید یه چیزی مساوی با 2 هست و پایین تر برابر با 3 کنیدش. (مگراینکه از کلید واژه mutable استفاده کنید)
اگه این موارد براتون گنگ و یا نامفهومه، شاید با خوندن ادامه متن و یا دیدن نمونه هایی از کد های تابعی برطرف بشه، در هر حالت سوالی بود میتونید بپرسید.
زبان هایی که میشناسید مثل پایتون، سی، جاوا، سیشارپ، سیپلاسپلاس، پیاچپی، جاوا اسکریپت، روبی و ... همه زبان های دستوری هستند که به مرور زمان بهشون ممکنه قابلیت های تابعی هم اضافه شده باشه.
بخش برنامه نویسی تابعی خیلی مفصله و داستان زیاد داره و من ازونجایی که نمیخوام این پست رو آکادمیک کنم و وارد مفاهیمی مثل Monad ها و محاسبات لامبدا و ... بشم به همین موارد بسنده میکنم، همین قدر هم برای شروع با این زبان ها و کلی کارای خلاقانه کردن کافیه پس از چیزی نترسید.
نمونه از از یک برنامه دستوری (Imperative) به زبان سیشارپ:
var list = new List<int> { 1, 2, 3 , 4}; int result = 0; for(int i = 0; i < list.Count; i++){ result += list[i]; } Console.WriteLine($"Sum: {result}");
همین برنامه با زبان افشارپ:
let result = List.sum [1; 2; 3; 4] printfn "Sum: %A" result
افشارپ ویژگی های مختلفی داره که ممکنه برات جالب باشه.
۱- سمی کالن
افشارپ برای اتمام دستوراتش به سمیکالن (;) نیاز نداره
۲- فاصله گذاری
اف شارپ مثل پایتون برای بلوک بندی کد از Indentation و فاصلهها استفاده میکنه، ولی نگرانش نباشید چون تو افشارپ این فاصله گذاریها ارورخیز نیست!
let x = "Hello" let number = if x.Length > 10 then 20 else 30
۳- توابع مانند متغیر ها شهروند درجه یک زبان هستن
توی اف شارپ تعریف توابع هیچ فرقی با تعریف مقادیر نداره و همشون با کلید واژه let ایجاد میشن و هرجایی هم میتونید مقدار و تابع تعریف کنید.
let myName = "Aryan" let myAge = 21 let addGreetingsToName name (age:int) = $"Hello {name}! You are {age} years old!" addGreetingsToName myName myAge
در کد بالا addGreetingsToName یک تابع هست. همون طور که احتمالا متوجه شدید توی افشارپ برای نوشتن پارامتر ها و یا دادن پارامتر تابع نیازی به نوشتن پرانتز و یا کاما ندارید و فقط با یک فاصله گذاشتن میتونید به پارامتر ها مقدار بدید (اگرچه میتونید به فرم سنتی پرانتز و کاما هم بنویسید). این نوع نحوه نوشتار یه مزیت خیلی بزرگ داره که تو بخش بعدی یعنی اعمال جزئی میبینیم.
۴- اعمال جزئی یا Partial Application
تو افشارپ میتونید توابع رو Partially Apply کنید، یعنی به تابعی کم تر از مقداری که نیاز داره پارامتر بدید. نتیجه تولید شده تابع جدیدیه که فقط پارامتر های پاس داده نشده رو نیاز داره.
let someFunction param1 param2 param3 = param1 + (2 * param2) - param3 let anotherFunction = someFunction 10 20 let result = anotherFunction 30 // calculates 10 + (2 * 20) - 30
در این مثال تابعی تعریف شده به اسم someFunction و سه پارامتر میگیره به اسم param1 تا param3. توی خط بعد من یه تابع جدید ساختم که با دادن پارامتر های اول و دوم، فقط یه پارامتر باقی مونده رو میگیره و مقدار زیر رو حساب میکنه:
10 + (2 * 20) + param3
۵- تعریف نوع های جدید و رکورد ها تو افشارپ کوتاه و آسونه
type Gender = | Male | Female | Other of string type Person = { name : string age : int father : Person option mother : Person option gender : Gender }
در نمونه کد بالا ما دو نوع تعریف کردیم یکی برای جنسیت، و یکی دیگه برای شخص. شخص با پارامتر های نام، سن و جنس تعریف شده، و همچنین دو ویژگی اختیاری (option) به نام پدر و مادر
۶- پایپ همه جا! (|>)
پایپ یا خط لوله ویژگی ای از افشارپه که همه جا تو افشارپ میبینیدش. پایپ هم یک اپراتور هست، مثل اپراتور های ریاضیات مثل جمع و ضرب با این تفاوت که پایپ یه مقدار رو به تابع جلوییش پاس میده و در واقع مثل یک خط تولید در کارخونه عمل میکنه:
let car = Car(name = "My Car") car |> paintBody green |> paintRoofs red |> testStrength |> drive
در این مثال ابتدا یک ماشین جدید ساختیم، و بعد با خط لوله پاسش دادیم به تابعی که بدنش رو سبز رنگ کنه، در مرحله بعد این ماشین با بدنه سبز رو به تابعی دادیم که سقفش رو قرمز میکنه، در مرحله بعدی سپردیمش به تیم تست قدرت ماشین، و در نهایت باهاش رانندگی کردیم.
۷- استاتیک تایپ همراه با استنتاج نوع (Type Inference)
زبان افشارپ یه زبان Static Type هست، یعنی اینکه زمان کامپایل نوع و تایپ همه چیز معلومه. به همین خاطر خیلی از خطا های برنامتون از جهات منطقی هنگام مرحله کامپایل گرفته میشه. از طرفی کامپایلر افشارپ باهوش هم هست و میتونه نوع پارامتر یا متغیر ها و تابع ها و غیره رو از روی کاربردش استنتاج کنه. به عنوان مثال اگه من در ادامه مثال بالا نمونه ای با دادن نام و جنسیت و ... تعریف کنم، خود کامپایلر میفهمه که من یک انسان رو تعریف کردم. در واقع شما مثل پایتون تایپ ها رو نمینویسید، ولی از اون طرف مزیت های استاتیک تایپینگ رو هم دارین.
// infers as Person let someGuy = { name = "Mohsen Mehdinia" age = 31 gender = Male father = None mother = None }
در این مثال دوم فرض کنید من میخوام تابعی بنویسم که لیستی از اسامی رو بگیره و به همه این اسامی خوشآمد بگه:
let greet names = names |> List.iter (fun name -> printfn "Hello %s!" name)
در کد بالا اول از همه تابع greet با پارامتر names رو تعریف کردیم و در بدنه تابع مقدار names رو پایپ کردیم داخل تابع iter که مخصوص پیمایش روی لیست و انجام کار روی هر آیتم از لیسته. این تابع باید بدونه که به ازای هر آیتم از لیست باید چه کاری انجام بده پس به یک تابع دیگه نیاز داره.
میتونیم این تابع رو یک جای دیگه تعریف کنیم، و یا همونجا جلوی iter با کلید واژه fun یک تابع جدید تعریف کنیم که پارامتر name رو میگیره و یه کاری باهاش انجام میده.
حالا بازی اصلی شروع میشه! کامپایلر اینجا خودش استنتاج میکنه که پارامتر names از نوع لیستی از string هاست و همچنین تابع greet از نوع:
string list -> unit
هست. این یعنی که این تابع لیستی رشته ها رو میگیره و یک unit برمیگردونه. unit یا واحد به این معنیه که تابع فقط یه کاری انجام داده و مقداری برنگردونده (در واقع میتونید به unit به همون شکل void در سایر زبان ها نگاه کنید). این فقط یه نمونه ساده بود و میتونید نمونه های خیلی پیچیده تری هم که کامپایلر قادر به تشخیص نوع هست رو هم در مثال ها ببینید.
۸- ترکیب توابع یا Composition
ترکیب توابع هم از مواردی هست که زیاد تو افشارپ میبینید. به مثال زیر نگاه کنید:
let countCharacter char (str:string) = str.ToCharArray() |> Array.filter (fun c -> c = char) |> Array.length let isMoreThanTen len = len > 10
اینجا دو تابع تعریف کردیم یکی به نام countChar که یک رشته و یک کاراکتر رو به عنوان پارامتر میگیره و میشمره که توی اون رشته چند بار کاراکتر مورد نظر ما دیده شده. همچنین تابعی وجود داره به اسم isMoreThanTen که یک عدد رو میگیره و میگه که اون عدد بیشتر از 10 هست یا نه. همون طور که حدس زدید هم تایپ این دو تابع به این شکله:
countChar : char → string→ int
isMoreThanTen : int → bool
حالا فرض کنید ما میخوایم تابعی بنویسیم که به ما بگه آیا تعداد کاراکتر های حرف b داخل یک متن بیشتر از 10 هست یا نه. خیلی راحت میتونیم این دو تابع رو به این شکل و با استفاده از اپراتور (>>) ترکیب کنیم:
let hasMoreThanTenB = countCharacter 'b' >> isMoreThanTen
و من حالا میتونم به این شکل فراخوانیش کنم:
hasMoreThanTenB "Oh baba, what a boooablabo"
تایپ این تابع نهایی هم به این شکل هست:
hasMoreThanTenB : string → bool
۹- تطبیق الگو یا Pattern Matching
با استفاده از این ویژگی زبان شما میتونید روی انواع و اقسام متغیر ها و توابع و زیر مجموعه هاشون الگویابی کنید و در صورت تطبیق الگو کار مورد نظرتون رو انجام بدید. (تطبیق الگو در زبان های برنامه نویسی یه جورایی مثل نوشتن RegEx روی رشته های زبان های طبیعی میمونه)
type Suit = | Diamond | Spades | Hearts | Clubs type Rank = | Ace | King | Queen | Jack | NumberRank of int type Card = Rank * Suit let getScore card = match card with | (Ace, Spades) -> 20 | (King, Hearts) | (Queen, Hearts) -> 15 | (NumberRank x, Spades) -> (x % 5) * 2 | _ -> 0
در نمونه کد بالا ما اول سه تایپ یکی برای شکل کارت، یکی دیگه برای مقدار روی کارت، و یکی هم برای خود کارت که ترکیبی از شکل و مقدار کارت هست تعریف کردیم. (علامت * برای ترکیب شکل و مقدار کارت که تایپ جدیدی تولید کرده، همون ضرب دکارتی ریاضیات روی مجموعه مقادیر هست که مجموعه مقادیر جدید ایجاد میکنه و از فضا نیومده)
حالا ما تابعی نیاز داریم که به ازای یک سری کارت خاص به کاربر امتیاز بدن، همون طور که میبینید این تابع به ازای آس پیک 20 امتیاز برمیگردونه، به ازای شاه یا بیبی دل 15 امتیاز، به ازای هر عددی از پیک ها، ابتدا اون عدد در پیمانه پنج حساب میشه و سپس ضرب در 2 شده و برگشت داده میشه، و در غیر این حالات امتیازی تعلق نمیگیره. میبینید که بدون نیاز به if های متوالی به شکل خیلی ساده همچین تابعی ممکن شد. زبان افشارپ از الگو های خیلی زیاد و متنوعی پشتیبانی میکنه که میتونید از مستنداتش مطالعه کنید.
۱۰- تهیه کننده نوع یا Type Provider
تایپ پروایدر ها، قابلیت هایی از زبان افشارپ هستن که براتون از یه فایل یا هر منبع دیگه ای تایپ تولید میکنن. به عنوان مثال شما بهش فایل csv یا json و ... به عنوان ورودی میدید و تایپ پروایدر براتون تجزیش میکنه و علاوه بر اینکه توی محیط توسعه تون براتون Auto Complete یا Intellisense میاره، تایپ چک هم میشه و اگه برنامه در رابطه با اون داده ها باگ داشته باشه، کامپایلر بهتون اطلاع میده.
۱۱- پشتیبانی کامل از داتنت و همکاری بدون مشکل با سایر زبان های داتنت مثل سیشارپ
افشارپ یکی از زبان های رسمی داتنته و میتونه از پروژه های سایر زبان های داتنت مثل پروژه هایی که با سی شارپ نوشته شدن استفاده کنه، و یا داخل یک Solution میتونید هم پروژه سیشارپ و هم افشارپ داشته باشید و این پروژه ها بدون مشکل هم رو صدا بزنن.
عملا هر مزیتی برای داتنت وجود داره، اف شارپ هم میتونه ازش استفاده کنه و گسترشش بده.
۱۲- سایر...
افشارپ کلی قابلیت خفن دیگه داره مثل واحد های اندازه گیری ( Units of Messure) که میتونید به هر متغیر عددی واحد بدید (مثل کیلوگرم، کیلو وات، گیگا بایت و ... حتی واحد های سفارشی خودتون) و این واحد ها توسط کامپایلر تایپ چک میشن، همچنین تایپ های ناشناس، ماژول سیستم خوب، ارتباط بومی با سیستم، Computational Expressions یا عبارات محاسباتی که یکی از قدرت مند ترین ساختار های زبانی افشارپه، بهینه سازی TailCall و خیلی چیز های دیگه... که توضیحشون خارج از این پست هست.
پایتون: همون طور که دیدیم، افشارپ مثل پایتون از Indentation استفاده میکنه و خیلی جاها نیازی به نوشتن تایپ ها نیست، لایبرری های خیلی خوبی برای هوش مصنوعی داره که میتونید مدل تون رو باهاش train کنید، در کنار اون استاتیک تایپ هست و اعتبار منطقی برنامه رو بالا میبره و در برنامه های بزرگ به باگ خیلی کمتری میخورید.
جاوا اسکریپت: افشارپ برای برنامه نویس های جاوا اسکریپت هم دوست خوبیه. چرا که با Fable عملا به لایبرری های گذشته تون دسترسی دارید و میتونید خیلی راحت برنامه نویسی Front-end انجام بدید.
سیشارپ: اگه برنامه نویس داتنت هستید، قطعا افشارپ رو یاد بگیرید. اولش ممکنه گیج کننده باشه، ولی بعدا دیگه حتی دوست ندارین به سیشارپ برگردین. دیفالت های افشارپ خیلی کدتون رو کمتر میکنه و در عین حال چون با دات نت آشنایی دارین، خیلی خیلی کارتون برای یادگیری آسون تر میشه.
هسکل (Haskell): هسکل به نوعی بابابزرگ زبان های تابعی شناخته میشه و اونقدر از نظر ساخت زبانی قوی و منطقی هست که نه تنها رو زبان های تابعی مثل افشارپ، بلکه روی زبان های mainstream هم تاثیر زیادی داشته. اگرچه بعضی از ویژگی های هسکل توی افشارپ نیست (قراره اضافه بشه)، ولی شما به کل اکوسیستم داتنت دسترسی پیدا میکنین. کم نبودن هسکلر هایی که بعد از کار با اف شارپ بهشون حس اینکه تو خونه خودشون هسکل هستن دست داده.
افشارپ برای توسعه رسما توسط مایکروسافت پشتیبانی میشه، و این یعنی اینکه شما میتونید خیلی راحت داخل ویژوال استودیو افشارپ بنویسید. همچنین اگه طرفدار محصولات JetBrains هستید و قبلا با PyCharm یا IntelliJIdea یا PhpStorm و ... تجربه داشتید، میتونید از JetBrains Rider استفاده کنید که از افشارپ پشتیبانی میکنه.
ولی جدای همه اینا اگه نظر منو بخواید، من افزونه Ionide برای VSCode رو بهتون توصیه میکنم. این افزودن قابلیت های خارق العاده ای داره که حتی برای زبان های پر استفاده تر هم پیدا نمیشن مثل ابزار های یکپارچه شده با بلید سیستم و یا قابلیت Info Panel. سایتش هم اینجاست:
خیلی هم عالی! منابع خیلی زیادی برای شروع وجود داره.
سایت «بنیاد افشارپ»
https://fsharp.org
سایت «افشارپ برای سرگرمی و سود»
https://fsharpforfunandprofit.com
سایت افشارپ در داتنت
https://dotnet.microsoft.com/languages/fsharp
این پایین بپرس ↓