در این فصل نکاتی را درباره برنامه نویسی دستوری در #F فرا خواهیم گرفت. برای شروع از mutale خواهیم گفت.
mutable Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک میگیریم و برای تغییر مقادیر شناسهها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمیشود)
نکته دوم :ابتدا به مثال زیر توجه کنید.
در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ میکنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده میکنیم.
همان
طور که میبینید شناسه دوم x بعد از تعریف دارای مقدار جدید Two بود و بعد
از اتمام محدوده(scope) مقدار x دوباره به One تغییر کرد.(بهتر است بگوییم
منظور از دستور print x سوم اشاره به شناسه x اول برنامه است). این رفتار
مورد انتظار ما در هنگام استفاده از روش تعریف مجدد شناسه هاست. حال به
بررسی رفتار muatable در این حالت میپردازیم.
تنها تفاوت در استفاده از mutable keyword و (->) است. خروجی مثال بالا نیز به صورت زیر خواهد بود. کاملا واضح است که مقدار شناسه x بعد از تغییر و اتمام محدوده(scope) هم چنان Two خواهد بود.
Reference Cells
روشی برای استفاده از شناسهها به صورت mutable است. با این روش میتونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اونها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسهها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده میشود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:
یک مثال:
خروجی مثال بالا 50 خواهد بود.
خروجی مثال بالا:
خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده میکنیم که برای یک متغیر مقدار وجود نداشته باشد. option در #F نوعی است که میتواند هم مقدار داشته باشد و هم نداشته باشد.
از None زمانی استفاده میکنیم که option مقدار نداشته باشد و از Some زمانی استفاده میکنیم که option مقدار داشته باشد.
در مثال بالا ورودی تابع exists از نوع int و به صورت اختیاری تعریف شده است.(معادل با ?int یا<Nullable<int در #C) در صورتی که x مقدار داشته باشد مقدار true در غیر این صورت مقدار false را برگشت میدهد.
چگونگی استفاده از option
مثال
یک تابع به نام tryFindMatch داریم با دو پارامتر ورودی. با استفاده از الگوی Matching از عنصر ابتدا تا انتها را در لیست (پارامتر list) با مقدار پارامتر pred مقایسه میکنیم. اگر مقادیر برابر بودند مقدار head در غیر این صورت None(یعنی option مقدار ندارد) برگشت داده میشود.
یک مثال کاربردی تر
در مثال بالا از optionها برای بررسی وجود یا عدم وجود فایلهای فیزیکی استفاده کردم.
Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F میپردازیم. ساختار کلی تعریف آن به صورت زیر است:
یک مثال از تعریف:
نحوه استفاده
enums فقط از انواع داده ای sbyte, byte, int16, uint16, int32, uint32, int64, uint16, uint64, char پشتیبانی میکند که البته مقدار پیش فرض آن Int32 است. در صورتی که بخواهیم صریحا نوع داده ای را ذکر کنیم به صورت زیر عمل میشود.
توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاسها بود میشود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی میکند.
در #F نیز امکان استفاده از این عبارت با اندکی تفاوت وجود دارد.مثال:
Units Of Measure
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحدهای اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحدهای اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص میکنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
یک مثال از تعریف واحد cm:
مثالی از تعریف میلی لیتر:
برای استفاده از این واحدها میتونید به روش زیر عمل کنید.
توابع تبدیل واحد ها
قدرت اصلی واحدهای اندازه گیری #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر میباشد:
یک مثال دیگر :
خروجی مثال بالا :
mutable Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک میگیریم و برای تغییر مقادیر شناسهها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
#1 let mutable phrase = "Can it change? " #2 printfn "%s" phrase #3 phrase <- "yes, it can." #4 printfn "%s" phrase
در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
Can it change? yes, it can.
نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
#1 let mutable phrase = "Can it change? " #3 phrase <- 1
اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمیشود)
Prog.fs(9,10): error: FS0001: This expression has type int but is here used with type string
let redefineX() = let x = "One" printfn "Redefining:\r\nx = %s" x if true then let x = "Two" printfn "x = %s" x printfn "x = %s" x
در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ میکنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده میکنیم.
Redefining: x = One x = Two x = One
let mutableX() = let mutable x = "One" printfn "Mutating:\r\nx = %s" x if true then x <- "Two" printfn "x = %s" x printfn "x = %s" x
Mutating: x = One x = Two x = Two
Reference Cells
روشی برای استفاده از شناسهها به صورت mutable است. با این روش میتونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اونها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسهها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده میشود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:
Member Or Field | Description | Definition |
(derefence operator)! | مقدار مشخص شده را برگشت میدهد | let (!) r = r.contents |
(Assignment operator)=: | مقدار مشخص شده را تغییر میدهد | let (:=) r x = r.contents <- x |
ref operator | یک مقدار را در یک reference cell جدید کپسوله میکند | let ref x = { contents = x } |
Value Property | برای عملیات get یا set مقدار مشخص شده | member x.Value = x.contents |
contents record field | برای عملیات get یا set مقدار مشخص شده | let ref x = { contents = x } |
let refVar = ref 6 refVar := 50
printfn "%d" !refVar
let xRef : int ref = ref 10 printfn "%d" (xRef.Value) printfn "%d" (xRef.contents) xRef.Value <- 11 printfn "%d" (xRef.Value) xRef.contents <- 12 printfn "%d" (xRef.contents)
10 10 11 12
خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده میکنیم که برای یک متغیر مقدار وجود نداشته باشد. option در #F نوعی است که میتواند هم مقدار داشته باشد و هم نداشته باشد.
let keepIfPositive (a : int) = if a > 0 then Some(a) else None
let exists (x : int option) = match x with | Some(x) -> true | None -> false
چگونگی استفاده از option
مثال
let rec tryFindMatch pred list = match list with | head :: tail -> if pred(head) then Some(head) else tryFindMatch pred tail | [] -> None let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] //برابر با 100 است let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]// برابر با None است
یک مثال کاربردی تر
open System.IO let openFile filename = try let file = File.Open (filename, FileMode.Create) Some(file) with | ex -> eprintf "An exception occurred with message %s" ex.Message None
Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F میپردازیم. ساختار کلی تعریف آن به صورت زیر است:
type enum-name = | value1 = integer-literal1 | value2 = integer-literal2 ...
type Color = | Red = 0 | Green = 1 | Blue = 2
let col1 : Color = Color.Red
type uColor = | Red = 0u | Green = 1u | Blue = 2u let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)
توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاسها بود میشود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی میکند.
using(var writer = new StreamWriter(filePath)) { }
let writeToFile fileName = use sw = new System.IO.StreamWriter(fileName : string) sw.Write("Hello ")
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحدهای اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحدهای اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص میکنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
[<Measure>] type cm
[<Measure>] type ml = cm^3
let value = 1.0<cm>
قدرت اصلی واحدهای اندازه گیری #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر میباشد:
[<Measure>] type g تعریف واحد گرم [<Measure>] type kg تعریف واحد کیلوگرم let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg> تعریف تابع تبدیل
[<Measure>] type degC // دما بر حسب سلسیوس [<Measure>] type degF // دما بر حسب فارنهایت let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF> // تابع تبدیل سلسیوس به فارنهایت let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>) // تابع تبدیل فارنهایت به سلسیوس let degreesFahrenheit temp = temp * 1.0<degF> // درجه به فارنهایت let degreesCelsius temp = temp * 1.0<degC> // درجه به سلسیوس printfn "Enter a temperature in degrees Fahrenheit." let input = System.Console.ReadLine() let mutable floatValue = 0. if System.Double.TryParse(input, &floatValue)// اگر ورودی عدد بود then printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>)) else printfn "Error parsing input."
خروجی مثال بالا :
Enter a temperature in degrees Fahrenheit. 90 That temperature in degrees Celsius is 32.22.