مقدمات زبان 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در واقع هنگام زمان اجرا نوع x و y توسط استنتاج خودکار مشخص می شوند . (در این مثال هر دو پارامتر x و y باید از یک نوع باشند زیرا نوع هر دو را 'a قرار دادیم. اگر بخواهیم انواع مختلف داشته باشیم باید پارامتر ها را اینگونه تعریف کنیم: (x: 'a) (y: 'b) )نکته: پارامترهای ضمنی را نمیتوانیم به صورت صریح تعریف کنیم. مثلا در تصویر بالا function1<int> a b اخطار خواهد داد:به متن warning دقت کنیدتعریف تابع صریح جنریکاگر بخواهیم نوع پارامتر(های) ورودی را صراحتا Generic معرفی کنیم (که به این نوع روش Explicitly Generic Constructs - ایجاد صریح جنریم) باید از پارامتر های نوع پس از نام تابع استفاده کنیم که در داخل <> قرار میگیرند و با , از هم جدا میشوند.Explicitly Generic Constructsدر تصویر بالا در اینجا از T به معنای Type استفاده میکنیم. همچنین نوع x و y باید می توانند از انواع مختلف باشند.تعیین نوع پارامترهای ورودی تابع صریح جنریک با استفاده از تعریف پارامتر نوع های مختلف که بیشتر برای نمایش نحوه کار کامپایلر میباشدبه امضای هر دو تابع دقت کنید. myfunc2 همان کار عکس قبلی (تابع بالایش) به روش ساده تر انجام داده استتصویر آخر بالا را تماشا کنید و به امضای myfunc و myfunc2 نگاه کنید. در تابع myfunc ما 3 پارامتر نوع مختلف و 3 پارامتر ورودی را تعریف کردیم ، نوع هر یک از پارامتر ورودی را یک پارامتر نوع قرار دادیم. امضای آن:مقدار unit همان نوع void در سایر زبان ها می باشدو نحوه استفاده از آنبا هر صدا زدن تابع باید انواع پارامتر های ورودی آن را هم مشخص کنیمحال به تابع myfunc2 و امضای آن توجه کنید:و نحوه استفاده از آننیازی به مشخص کردن نوع پارامتر های ورودی در هر بار صدا زدن ندارد و از طریق استنتاج نوع در زمان اجرا متوجه نوع آنها میشودنحوه استفاده از تابع صریح جنریک و تابع ضمنی جنریک:تعریف پارامتر صریح و ضمنی با همگاهی لازم است تعدادی از پارامترهای نوع را به صورت صریح و تعدادی از آنها را به صورت ضمنی و با کمک کامپایلر مشخص کنیم.برای این کار کافیست بجای پارامترهای نوعی که می خواهیم مقدار آن را به صورت ضمنی توسط کامپایلر مشخص شود از کاراکتر _ استفاده کنیم:ایجاد محدودیت برای تابع جنریک پارامتر های نوع را می توان به داشتن یک یا چند شرط خاص (Generic Constraints) محدود کرد. مثال:به امضای تابع مساوی دقت کنیدتابع عملگر = بالا را در نظر بگیرید. فقط دو عملوند جنریکی را دریافت میکند که از برابری پشتیبانی کنند (when a' : equality) مثال دیگر:با شرط (when 'a : comparison) اگر نوع مقادیر ارسالی به تابع، نوعی باشند که مقایسه برایش تعریف نشده باشد برنامه با خطا مواجه خواهد شد.به 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) 45منبع این بخشادامه دارد ... فقط منتشر شو لعنتی