یک برنامه نویس که هرآنچه را که یاد میگیرد در دفترچه یادداشت ویرگولیش یادداشت میکرد(!) حتی یک خط ! تا درصورت نیاز به آن رجوع کند...
مقدمات زبان F# توابع (قسمت ششم)
توابع در F# ماننده بقیه زبان ها دارای نام ، پارامتر (های) ورودی (اختیاری) ، بدنه و یک مقدار خروجی است. استنتاج خودکار نوع (که قبلا گفته شد) برای توابع هم کار خواهد کرد و نیازی به مشخص کردن صریح نوع ها (مانند نوع ورودی ها و خروجی) نیست(البته که مشخص کنیم بهتر خواهد بود).
تعریف تابع
تعریف تابع مانند تعریف مقدار است با این تفاوت که بعد از نام پارامتر های ورودی را مشخص میکنیم. ساختار کلی به صورت زیر می باشد:
برخلاف سایر زبان ها مانند C# که از کلمه کلیدی return برای بازگرداندن نتیجه تابع استفاده میشود ، این کار در F# مقدار آخرین عبارت از بدنه تابع که توسط کامپایلر ارزیابی شود را به عنوان خروجی آن در نظر میگیرد:
مشخص کردن نوع ورودی و خروجی تابع قبل توسط خودمان:
در تصویر بالا میبینید که ما به کامپایلر از طریق مشخص کردن نوع های پارامتر های ورودی و خروجی ،مشخص می کنیم که تابع چه نوع ورودی و خروجی را دریافت کند. به غیر از نوع مشخص نوع دیگری را به تابع بدهیم کامپایلر به ما خطا میدهد.
به نوع یک تابع امضا یا Signature آن تابع نیز گفته میشود. امضا تابع قبل:
cylinderVolume : radius:float -> length:float -> float
همیشه آخرین مورد در امضا تابع ، خروجی تابع میباشد.
توابع Generic
در حالت عادی پارامتر های ورودی هر تابع دارای نوع مشخصی هستند از این رو در صورتی که مقادیر ارسال شده به یک تابع مغایر با نوع پارامترهای آن باشد کامپایلر خطا میدهد. برای سازگاری تابع با هر نوعی باید از توابع جنریک استفاده کنیم. توابع جنریک تمام نوع داده ای سازگارند و می توانند هر مقدار ورودی را بپذیرند.
تعریف تابع ضمنی جنریک
برای ایجاد توابع جنریک باید پارامتر های تابع را به صورت Generic معرفی کنیم.(این کاریست که اغلب توسط کامپایلر و استنتاج نوع خودکار انجام میشود که به آن Implicitly Generic Constructs- ایجاد ضمنی جنریک گفته میشود)
روش دیگر ایجاد ضمنی جنریک این است که هر پارامتر را به صورت parameter-name :`a (که منظور از a به معنی any میباشد ولی شما می توانید نام دیگری را در نظر بگیرید) تعریف کنیم
در واقع هنگام زمان اجرا نوع x و y توسط استنتاج خودکار مشخص می شوند . (در این مثال هر دو پارامتر x و y باید از یک نوع باشند زیرا نوع هر دو را 'a قرار دادیم. اگر بخواهیم انواع مختلف داشته باشیم باید پارامتر ها را اینگونه تعریف کنیم:
(x: 'a) (y: 'b) )
نکته: پارامترهای ضمنی را نمیتوانیم به صورت صریح تعریف کنیم. مثلا در تصویر بالا function1<int> a b اخطار خواهد داد:
تعریف تابع صریح جنریک
اگر بخواهیم نوع پارامتر(های) ورودی را صراحتا Generic معرفی کنیم (که به این نوع روش Explicitly Generic Constructs - ایجاد صریح جنریم) باید از پارامتر های نوع پس از نام تابع استفاده کنیم که در داخل <> قرار میگیرند و با , از هم جدا میشوند.
در تصویر بالا در اینجا از T به معنای Type استفاده میکنیم. همچنین نوع x و y باید می توانند از انواع مختلف باشند.
تصویر آخر بالا را تماشا کنید و به امضای myfunc و myfunc2 نگاه کنید. در تابع myfunc ما 3 پارامتر نوع مختلف و 3 پارامتر ورودی را تعریف کردیم ، نوع هر یک از پارامتر ورودی را یک پارامتر نوع قرار دادیم. امضای آن:
و نحوه استفاده از آن
حال به تابع myfunc2 و امضای آن توجه کنید:
و نحوه استفاده از آن
نحوه استفاده از تابع صریح جنریک و تابع ضمنی جنریک:
تعریف پارامتر صریح و ضمنی با هم
گاهی لازم است تعدادی از پارامترهای نوع را به صورت صریح و تعدادی از آنها را به صورت ضمنی و با کمک کامپایلر مشخص کنیم.برای این کار کافیست بجای پارامترهای نوعی که می خواهیم مقدار آن را به صورت ضمنی توسط کامپایلر مشخص شود از کاراکتر _ استفاده کنیم:
ایجاد محدودیت برای تابع جنریک
پارامتر های نوع را می توان به داشتن یک یا چند شرط خاص (Generic Constraints) محدود کرد. مثال:
تابع عملگر = بالا را در نظر بگیرید. فقط دو عملوند جنریکی را دریافت میکند که از برابری پشتیبانی کنند (when a' : equality)
مثال دیگر:
با شرط (when 'a : comparison) اگر نوع مقادیر ارسالی به تابع، نوعی باشند که مقایسه برایش تعریف نشده باشد برنامه با خطا مواجه خواهد شد.
نکته : برای فراخوانی یک Generic بر اساس نام آن می توانیم از 2 روش استفاده کنیم:
برای نمونه فراخوانی لیست list<int> و int list می باشد.
توابع تو در تو
در F# می توانیم توابع تو در تو تعریف کنیم کار زمانی مفید است که بخواهیم تابع اصلی را به توابع فرعی تقسیم کنیم. دسترسی به توابع داخلی یک تابع فقط از طریق تابع پدر امکان پذیر است.
محدوده Scope عملکرد
هرکدام از مقادیر و توابع در اف شارپ دارای محدوده مشخصی هستند که فقط در آن محدوده می توان به آنها دسترسی داشت و از آنها استفاده کرد. به طور پیش فرض مقادیر و توابع دارای محدوده ماژول (Module Scope) می باشند، به این معنی که هر مقدار یا تابعی که در یک ماژول تعریف شود، از آن به بعد در هر جایی از آن ماژول قابل استفاده خواهد بود. البته مقادیر و توابعی که داخل یک تابع دیگر تعریف شوند دارای محدوده محلی (Local Scope) هستند و فقط در داخل همان تابع قابل استفاده اند و دسترسی به آنها از بیرون تابع امکان پذیر نیست(به این گونه مقادیر، مقادیر محلی نیز گفته می شود). امکان استفاده از نام تکراری در توابع تودرتو وجود دارد زیرا shadowing یا پنهان سازی اتفاق می افتد
توابع Inline
اگر تعداد فراخوانی های یک تابع زیاد باشد(فراخوانی، اجرا و بازگشت نتیجه) در سرعت اجرای برنامه تاثیرگذار خواهد بود.در چنین شرایطی بهتر است از توابع به صورت Inline استفاده کنیم.استفاده از این توابع باعث میشود یک کپی از بدنه تابع مورد نظر را در داخل برنامه قرار دهد. (فکر میکنم چیزی شبیه متد static در c#)
مورد دیگر استفاده توابع درون خطی در بحث پارامترهای نوع Static است. در f# پارامترهای نوع به دو دسته Generic و Static تقسیم می شوند. پارامتر نوع generic که قبلا هم گفتیم (parameterName : 'a) مانند جنریک ها در c# هستند. اما پارامتر نوع Static فقط در F# هستند که به صورت parameterName : ^x نوشته میشوند و و فقط در توابع inline قابل استفاده هستند. تفاوت در پارامتر نوع generic و پارامتر نوع static در این است که : نوع واقعی پارامترنوع generic در زمان اجرای برنامه مشخص میشود در حالی که نوع واقعی پارامترنوع Static قبل از اجرای برنامه و در زمان کامپایل مشخص شده است
به عبارت دیگر :
هنگامی که از پارامترهای نوع استاتیک استفاده می کنید ، باید هر تابع ای که با پارامترهای نوع ، پارامتر می شود درون خطی باشد. این تضمین می کند که کامپایلر می تواند این پارامترهای نوع را برطرف کند. وقتی از پارامترهای نوع معمولی استفاده می کنید ، چنین محدودیتی وجود ندارد.
به غیر از امکان استفاده از محدودیت های member ، توابع درون خطی می توانند در بهینه سازی کد مفید باشند. با این حال ، استفاده بیش از حد از توابع درون خطی می تواند باعث شود تا کد شما در برابر تغییرات بهینه سازی کامپایلر و اجرای توابع کتابخانه مقاومت کمتری کند. به همین دلیل ، باید از به کار بردن توابع درون خطی برای بهینه سازی خودداری کنید ، مگر اینکه تمام تکنیک های بهینه سازی دیگر را امتحان کرده باشید. ایجاد یک تابع یا متد درون خطی می تواند گاهی اوقات عملکرد را بهبود ببخشد ، اما همیشه اینگونه نیست. بنابراین ، شما باید performance تابع درون خطی را برای تأیید اینکه ساختن این تابع دارای تأثیر مثبت است ، اندازه گیری کنید.
اصلاح کننده inline
می تواند برای توابع در سطح بالا ، در سطح ماژول یا در سطح متد در یک کلاس استفاده شود.
برای تعریف توابع درون خطی کافیه کلمه کلیدی inline را قبل از نام تابع بیاوریم:
نکته: برای توابع درون خطی (static هستند) باید نوع پارامترنوع از نوع static باشد در غیر این صورت با خطا مواجه خواهید شد.(بجای علامت ' از علامت ^ استفاده کنید!)
توابع بی نام با استفاده از عبارات لامبدا Lambda Expressions
توابع بدون نام ، توابع کوچکی هستند که بصورت تک خطی داخل برنامه های F# مورد استفاده قرار می گیرند. از آنجایی که بی نام هستند پس نمیتوان آنها را با نام فراخوانی کرد.
کامه کلیدی fun (که مخفف function است) بعد مشخص کردن لیست پارامتر های ورودی و بعد از علامت <- بدنه تابع نوشته میشود. مثال ها :
و استفاده در عمل :
نکته: نوع خروجی توابع بدون نام را نمی توان به صورت صریح مشخص کرد.
نکته:مقدار پارامترهایی که می خواهیم از خارج از متد بی نام به آن پاس دهیم (منتقل کنیم) در آخر و خارج از پرانتز می آوریم:
(fun x-> x + 1) 4
5
ادامه دارد ... فقط منتشر شو لعنتی
مطلبی دیگر از این انتشارات
کارایی الگوریتمی و نقش آن در پیشرفت هوش مصنوعی
مطلبی دیگر از این انتشارات
با هوش مصنوعی یک قدم جلوتر از مصرف کنندگان باشید!
مطلبی دیگر از این انتشارات
گزارش تحلیلی اندازه بازار شهر هوشمند (پیشبینی سال ۲۰۲۳ میلادی)