مقدمات زبان F# توابع (قسمت ششم)

توابع در F# ماننده بقیه زبان ها دارای نام ، پارامتر (های) ورودی (اختیاری) ، بدنه و یک مقدار خروجی است. استنتاج خودکار نوع (که قبلا گفته شد) برای توابع هم کار خواهد کرد و نیازی به مشخص کردن صریح نوع ها (مانند نوع ورودی ها و خروجی) نیست(البته که مشخص کنیم بهتر خواهد بود).

تعریف تابع

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

در ساختار مقادیر اختیاری را در  [ و ] نشان میدهیم
در ساختار مقادیر اختیاری را در [ و ] نشان میدهیم

برخلاف سایر زبان ها مانند C# که از کلمه کلیدی return برای بازگرداندن نتیجه تابع استفاده میشود ، این کار در F# مقدار آخرین عبارت از بدنه تابع که توسط کامپایلر ارزیابی شود را به عنوان خروجی آن در نظر میگیرد:

استنتاج خودکار
استنتاج خودکار

مشخص کردن نوع ورودی و خروجی تابع قبل توسط خودمان:

در تصویر بالا میبینید که ما به کامپایلر از طریق مشخص کردن نوع های پارامتر های ورودی و خروجی ،مشخص می کنیم که تابع چه نوع ورودی و خروجی را دریافت کند. به غیر از نوع مشخص نوع دیگری را به تابع بدهیم کامپایلر به ما خطا میدهد.


به نوع یک تابع امضا یا Signature آن تابع نیز گفته میشود. امضا تابع قبل:

cylinderVolume : radius:float -> length:float -> float

همیشه آخرین مورد در امضا تابع ، خروجی تابع میباشد.

منبع این بخش




توابع Generic

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

تعریف تابع ضمنی جنریک

برای ایجاد توابع جنریک باید پارامتر های تابع را به صورت Generic معرفی کنیم.(این کاریست که اغلب توسط کامپایلر و استنتاج نوع خودکار انجام میشود که به آن Implicitly Generic Constructs- ایجاد ضمنی جنریک گفته میشود)

به امضا دقت کنید
به امضا دقت کنید

روش دیگر ایجاد ضمنی جنریک این است که هر پارامتر را به صورت parameter-name :`a (که منظور از a به معنی any میباشد ولی شما می توانید نام دیگری را در نظر بگیرید) تعریف کنیم

Implicitly Generic Constructs
Implicitly Generic Constructs

در واقع هنگام زمان اجرا نوع x و y توسط استنتاج خودکار مشخص می شوند . (در این مثال هر دو پارامتر x و y باید از یک نوع باشند زیرا نوع هر دو را 'a قرار دادیم. اگر بخواهیم انواع مختلف داشته باشیم باید پارامتر ها را اینگونه تعریف کنیم:
(x: 'a) (y: 'b) )

نکته: پارامترهای ضمنی را نمیتوانیم به صورت صریح تعریف کنیم. مثلا در تصویر بالا function1<int> a b اخطار خواهد داد:

به متن warning دقت کنید
به متن warning دقت کنید


تعریف تابع صریح جنریک

اگر بخواهیم نوع پارامتر(های) ورودی را صراحتا Generic معرفی کنیم (که به این نوع روش Explicitly Generic Constructs - ایجاد صریح جنریم) باید از پارامتر های نوع پس از نام تابع استفاده کنیم که در داخل <> قرار میگیرند و با , از هم جدا میشوند.

Explicitly Generic Constructs
Explicitly Generic Constructs

در تصویر بالا در اینجا از T به معنای Type استفاده میکنیم. همچنین نوع x و y باید می توانند از انواع مختلف باشند.

تعیین نوع پارامترهای ورودی تابع صریح جنریک با استفاده از تعریف پارامتر نوع های مختلف که بیشتر برای نمایش نحوه کار کامپایلر میباشد
تعیین نوع پارامترهای ورودی تابع صریح جنریک با استفاده از تعریف پارامتر نوع های مختلف که بیشتر برای نمایش نحوه کار کامپایلر میباشد


به امضای هر دو تابع دقت کنید. myfunc2 همان کار عکس قبلی (تابع بالایش) به روش ساده تر انجام داده است
به امضای هر دو تابع دقت کنید. myfunc2 همان کار عکس قبلی (تابع بالایش) به روش ساده تر انجام داده است


تصویر آخر بالا را تماشا کنید و به امضای myfunc و myfunc2 نگاه کنید. در تابع myfunc ما 3 پارامتر نوع مختلف و 3 پارامتر ورودی را تعریف کردیم ، نوع هر یک از پارامتر ورودی را یک پارامتر نوع قرار دادیم. امضای آن:

مقدار unit همان نوع void در سایر زبان ها می باشد
مقدار unit همان نوع void در سایر زبان ها می باشد

و نحوه استفاده از آن

با هر صدا زدن تابع باید انواع پارامتر های ورودی آن را هم مشخص کنیم
با هر صدا زدن تابع باید انواع پارامتر های ورودی آن را هم مشخص کنیم

حال به تابع myfunc2 و امضای آن توجه کنید:

و نحوه استفاده از آن

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


نحوه استفاده از تابع صریح جنریک و تابع ضمنی جنریک:




تعریف پارامتر صریح و ضمنی با هم

گاهی لازم است تعدادی از پارامترهای نوع را به صورت صریح و تعدادی از آنها را به صورت ضمنی و با کمک کامپایلر مشخص کنیم.برای این کار کافیست بجای پارامترهای نوعی که می خواهیم مقدار آن را به صورت ضمنی توسط کامپایلر مشخص شود از کاراکتر _ استفاده کنیم:



ایجاد محدودیت برای تابع جنریک

پارامتر های نوع را می توان به داشتن یک یا چند شرط خاص (Generic Constraints) محدود کرد. مثال:

به امضای تابع مساوی دقت کنید
به امضای تابع مساوی دقت کنید

تابع عملگر = بالا را در نظر بگیرید. فقط دو عملوند جنریکی را دریافت میکند که از برابری پشتیبانی کنند (when a' : equality)

مثال دیگر:

با شرط (when 'a : comparison) اگر نوع مقادیر ارسالی به تابع، نوعی باشند که مقایسه برایش تعریف نشده باشد برنامه با خطا مواجه خواهد شد.

به n تعداد می توان محدودیت تعریف کرد
به n تعداد می توان محدودیت تعریف کرد


نکته : برای فراخوانی یک 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

منبع این بخش


ادامه دارد ... فقط منتشر شو لعنتی