برنامه نویسی به زبان ارلنگ

ارلنگ زبان برنامه نویسی تابعی و همروند است

برنامه نویسی به زبان ارلنگ

ارلنگ زبان برنامه نویسی تابعی و همروند است

برنامه نویسی به زبان ارلنگ

در این وبلاگ مطالب مربوط به ارلنگ و الیکسیر منتشر خواهد شد

۲۶ مطلب با کلمه‌ی کلیدی «ارلنگ» ثبت شده است

  • ۰
  • ۰

این مثال ساده در حقیقت سعی دارد شما را با عملگرهای div و rem آشنا کند که اولی تقسیم صحیح و دومی باقیمانده است. در این مثال شما باید برنامه‌ئی بنویسید که طول و عرض یک دیوار را دریافت کند( با در نظر گرفتن این نکته که برای رنگ کردن هر 350 فوت مربع یه گالون رنگ نیاز است و همچنین امکان خرید قسمتی از یک گالون نیست و باید یک گالون کامل بخریم) و بشما اعلام کند چه تعداد گالون نیاز دارید. برای مثال اگر مساحت دیوار 351 فوت مربع باشد شما به دو گالون رنگ نیاز دارید.

-module(ex9).
-export([main/0]).
-define(GALLON, 350).

main() ->
	try run()
	catch
		error:_Error -> io:format(
			"Please Enter only Numeric type~n");
		throw:negativeNumber -> io:format(
			"Please Enter only non-zero positive number~n")
	end.

run() ->
	{Length, Width} = readDimension(),
	D = calc_gallon(Length,Width),
	io:fwrite(
		"You will need to purchase ~w gallons of paint 
		to cover ~w square feet.~n",
		[D,Length*Width]).

calc_gallon(Length, Width) ->
	D = Length * Width,
	if
		D div ?GALLON == D / ?GALLON -> D div ?GALLON;
		true -> (D div ?GALLON) + 1
	end.
	
readDimension() ->
	{ok,[First]} = io:fread(
		"What is the length of the wall in feet? ","~d"),
	{ok,[Second]} = io:fread(
		"What is the width of the wall in feet? ","~d"),
	if
		First =< 0 orelse Second =< 0 -> throw(negativeNumber);
		true -> ok
	end,
	{First,Second}.
  • مهدی حسینی مقدم
  • ۰
  • ۰

برنامه‌ای بنویسد که با دریافت تعداد نفرات یک مهمانی و همچنین تعداد پیتزای موجود و البته تعداد تکه‌های هر پیتزا محاسبه کند که بهر نفر چند تکه می‌رسد. قطعات قابل ریز شدن نیستند و بنابرین سهم هر نفر عدد صحیح خواهد بود و طبیعتا در برخی موارد تعدادی  پیتزا باقی خواهد ماند. بنابراین برنامه سهم نفر و تعداد باقی مانده را خواهد نوشت. برای مثال ورودی های زیر خروجی نوشته شده را خواهد داد:

Input:

How many people? 4

How many pizzas do you have? 5

How many slices each pizza have? 7

Output:

4 People with 5 pizzas which have 7 slices per each

Each Person will have 8 slices

there are 3 leftover

کد این برنامه بشرح زیر خواهد بود:

 

-module(ex8).
-export([main/0]).

main() ->
	try run()
	catch
		error:_Error -> io:format("Please Enter only Numeric type~n");
		throw:negativeNumber -> io:format(
			"Please Enter only non-zero positive number~n")
	end.

run() ->
	{Poeple, Pizzas, Slices} = readServing(),
	io:fwrite("~w People with ~w pizzas which have ~w slices per each~n",
		[Poeple,Pizzas,Slices]),
	io:fwrite("Each Person will have ~w slices~n there are ~w leftover~n",
		[(Pizzas*Slices) div Poeple, (Pizzas*Slices) rem Poeple]).

	
readServing() ->
	{ok,[Poeple]} = io:fread(
		"How many people? ","~d"),
	{ok,[Pizzas]} = io:fread(
		"How many pizzas do you have? ","~d"),
	{ok,[Slices]} = io:fread(
		"How many slices each pizza have? ","~d"),
	if
		Poeple =< 0 orelse Pizzas =< 0 orelse Slices =< 0 -> throw(negativeNumber);
		true -> ok
	end,
	{Poeple,Pizzas,Slices}.
  • مهدی حسینی مقدم
  • ۰
  • ۰

مثال بعدی محاسبه مساحت یک مستطیل است. واحد دریافتی از کاربر فوت هست و خروجی باید مساحت به فوت مربع و متر مربع باشد. برای تبدیل فوت مربع به متر مربع باید آن را در عدد ثابت 0.09290304 ضرب کنیم. این عدد را بصورت ماکرو در ابتدای برنامه تعریف کردم. برای استفاده از ماکرو در ارلنگ نام آن را که صرفا بخاطر یک قرارداد بصورت حروف بزرگ نوشته می‌شود را با ؟ فراخوانی می‌کنیم، کد زیر برنامه مورد نظر است:

-module(ex7).
-export([main/0]).
-define(CONRATE,0.09290304).

main() ->
	try run()
	catch
		error:_Error -> io:format("Please Enter only Numeric type~n");
		throw:negativeNumber -> io:format(
			"Please Enter only non-zero positive number~n")
	end.

run() ->
	{Length, Width} = readDimension(),
	D = calc_area(Length,Width),
	io:fwrite("You entered dimensions of ~w feet by ~w feet.~n",
		[Length,Width]),
	io:fwrite("The area is~n ~w square feet~n ~w square meters~n",
		[D, D * ?CONRATE]).

calc_area(Length, Width) ->
	Length * Width.
	
readDimension() ->
	{ok,[First]} = io:fread(
		"What is the length of the room in feet? ","~d"),
	{ok,[Second]} = io:fread(
		"What is the width of the room in feet? ","~d"),
	if
		First =< 0 orelse Second =< 0 -> throw(negativeNumber);
		true -> ok
	end,
	{First,Second}.
  • مهدی حسینی مقدم
  • ۰
  • ۰

این مثال بسیار شبیه مثال قبلی است. دو عدد از ورودی در یافت می‌شود. اولی سن شماست و دومی سنی که دوست دارید بازنشسته شوید. سپس قرار است بشما بگوید که چند سال تا بازنشستگی دارید ( که خیلی ساده با کسر کردن سن کنونی از سن مورد نظر برای بازنشستگی بدست می‌آید) و چه سالی بازنشسته خواهید شد. این قسمت کمی چالشی است زیرا شما نیاز دارید با توابع خاصی سال کنونی را از سیستم استخراج کنید. تابع مورد نظر local_time است که در ماژول سیستمی calendar موجود است.  از آنجایی که ما تنها به سال کنونی نیاز داریم باقی را نادیده می گیریم. خروجی این تابع بصورت زیر است:

{{Year, Month, Day}, {Hour, Minute, Second}}
کد برنامه به شرح زیر است: 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-module(ex6).
-export([main/0]).

main() ->
	try run()
	catch
		error:_Error -> io:format("Please Enter only Numeric type~n");
		throw:negativeNumber -> io:format("Please Enter only positive number~n");
		throw:outOfBound -> io:format("You are already retired~n")
	end.
	
run() ->
	{Cur,Retire} = readInteger(),
	{{Year,_,_},_} = calendar:local_time(),
	io:fwrite("you have ~w years before retirement~n",[Retire - Cur]),
	io:fwrite("It's ~w so you be retired in ~w ~n",[Year,Year+(Retire - Cur)]).

readInteger() ->
	{ok,[First]} = io:fread("What's you current age? ","~d"),
	{ok,[Second]} = io:fread("At what age would you like to retire? ","~d"),
	if
		First < 0 orelse Second < 0 -> throw(negativeNumber);
		First > Second -> throw(outOfBound);
		true -> ok
	end,
	{First,Second}.

خط 14 ما تنها مقدار سال را به متغیر Year انتقال می‌دهیم و باقی را دور می‌ریزیم. ضمنا سیستم برای مواقعی که سن مورد نظر برای بازنشستگی کمتر از سن کنونی شماس خطا خواهد داد. 

  • مهدی حسینی مقدم
  • ۰
  • ۰

مثال بعدی در ظاهر بسیار ساده است. برنامه‌ئی بنویسید که دو عدد را از ورودی بگیرد و چهار عمل اصلی را با آن دو عدد انجام دهد. برای نمونه اگر بترتیی 4و 5 را وارد کنید باید خروجی مشابه زیر داشته باشیم:

4 + 5 = 9

4 – 5 = -1

4 * 5 = 20

4 / 5 = 0

 البته تقسیم در محاسبات صحیح فرض شده است. برای حل کردن این مثال مانند مثال‌های گذشته براحتی می‌توان با ورودی دریافت شده از کاربر کار کرد با این تفاوت که باید ابتدا مطمئن شویم که ورودی عدد است و نه چیز دیگر. ضمنا برای این مثال ورودی عدد منفی را نیز از کاربر نخواهیم پذیرفت. کد این برنامه بصورت زیر است:

-module(ex5).
-export([main/0]).
main() ->
	try calc()
	catch
		error:_Error -> io:format("Please Enter only Numeric type~n");
		throw:negativeNumber -> ("Please Enter only positive number~n")
	end.
		
calc() ->
	{A,B} = readInteger(),
	if
		A < 0 orelse B < 0 -> throw(negativeNumber);
		true -> ok
	end,
	[print(A,B,Op) || Op <- ["+","-","*","/"]].
		
readInteger() ->
	{ok,[First]} = io:fread("Enter the first number: ","~d"),
	{ok,[Second]} = io:fread("Enter the second number: ","~d"),
	{First,Second}.

print(A,B,Op) ->
	Res = case Op of
			"+" -> A + B;
			"*" -> A * B;
			"-" -> A - B;
			"/" -> A div B
			end,
	io:fwrite("~w ~s ~w = ~w ~n",[A, Op, B, Res]).

بطور مشخص در کد بالا سیاست ارلنگ در مواجهه با استثناءها قابل مشاهده است. سیاستی که اصطلاحا "اجازه بده خراب شود" نام دارد. به این معنی که جلوگیری از خطا و باگ ها تقریبا غیر ممکن است و بهتر است بجای آن تلاش برای کنترل شرایط پس از خطا صرف شود. در مقابل این سیاست، برنامه نویسی تدافعی defensive programming قرار دارد که سعی دارد هر خطا را در همان محل بررسی کند. برای این منظور عبارات کنترلی خطا (try/catch ) تنها در تابع آخر قرار خواهد گرفت و سایر توابع صرفا کاری برای کنترل و پیشگیری خطا انجام نخواهد داد. البته در بسیاری از زبانهای شئ‌گرا هم مدل برنامه نویسی تدافعی کنار گذاشته شده است.

در کد بالا تابع main خطا ها را بررسی می‌کند و تنها تابع calc را صدا می‌زند. این تابع سپس تابع readInteger را صدا می‌زند که وظیفه‌اش خواندن دو مقدار عددی است. و در آخر هم تابع print صدا زده می‌شود.

  • مهدی حسینی مقدم
  • ۰
  • ۰

این مثال هم مانند قبلی ها بسیار ساده است، چهار مقدار ورودی می‌گیرد و یک عبارت چاپ می‌کند. برخلاف مثال قبل از trim استفاده نکردم. trim کارکترهای خالی مانند تب و اینتر را از ابتدا و انتهای ورودی حذف می‌کند که شاید خیلی دقیق نباشد زیرا ما تنها می‌خواهیم کارکتر خط جدید را از انتها حذف کنیم، برای همین از تابع دیگری استفاده کردم. نکته دیگر استفاده از export_all است که البته صرفا برای تست مناسب است.

-module(ex4).
-compile(export_all).

main() ->
	Noun = string:strip(io:get_line("Enter a noun:"),right,$\n),
	Verb = string:strip(io:get_line("Enter a verb:"),right,$\n),
	Adjective = string:strip(io:get_line("Enter an adjective:"),right,$\n),
	Adverb = string:strip(io:get_line("Enter an adverb: "),right,$\n),
	io:fwrite("Do you ~s your ~s ~s ~s? That's hilarious!~n",[Verb, Adjective, Noun, Adverb]).
  • مهدی حسینی مقدم
  • ۰
  • ۰

برنامه‌ئی بنویسید که دو مقدار رشته یکی برای نویسنده و یکی برای سخن آن نویسنده را از ورودی بگیرد و سپس آن را با فرمت زیر نمایش دهد. دقت کنید که کارکتر های خاص نظیر " نیز در رشته‌ی ورودی وجود خواهند داشت. برنامه باید مانند مثال زیر عمل کند:

What is the quote? These aren't the droids you're looking for.
Who said it? Obi-Wan Kenobi
Obi-Wan Kenobi says, "These aren't the droids you're looking for."


-module(ex3).
-export([prompt/0]).

prompt() ->
	Quote = string:trim(io:get_line("What is the quote? ")),
	Author = string:trim(io:get_line("Who said it? ")),
	io:fwrite("~s said \"~s\" ~n",[Author, Quote]).

من در حل این جواب از تابع get_line استفاده کردم. البته این تابع یک کارکتر خط جدید نیز به انتهای رشته اضافه می‌کند بنابراین مجبور به استفاده از تابع trim شدم که فضاهای خالی اول و آخر عبارت را حذف کند. همچنین به دلیل اینکه در متن نهایی باید " چاپ شود قبل از آن از کارکتر / استفاده کردم. 

  • مهدی حسینی مقدم
  • ۰
  • ۰

در این مثال ساده، قرار بر نوشتن برنامه‌ئی است که یک رشته از ورودی بگیرد و طول آن را چاپ کند. البته ارلنگ نوع رشته ندارد و رشته ها در ارلنگ به دو صورت باینری و لیست ذخیره می‌شوند. خروجی این برنامه بصورت باینری ذخیره می‌گردد و بنابرین نیاز به استفاده از توابع کتابخانه خاصی است.

%compute length of a string
-module(ex2).
-export([prompt/0]).

prompt() ->
	{ok,S} = io:fread("What is the string? ","~s"),
	io:fwrite("the length of ~s is ~w ~n",[S,string:length(S)]).
  • مهدی حسینی مقدم
  • ۰
  • ۰

تصمیم گرفتم مثالهای کتاب Exercises for programmer را به زبان ارلنگ انجام دهم و پس اطمینان از درستی آنها، منتشر کنم. مثال اول که بسیار ساده بود. از کاربر اسمش را سوال کنم و عبارت Hello را در ابتدای آن گذاشته و سپس در خروجی چاپ کند.

 کد بشرح زیر است:

-module(ex1).

-export([hello/0]).

hello() ->
    {ok,S} = io:fread("What is your name? ","~s"),
    io:fwrite("Hello ~s~n",[S]).
  • مهدی حسینی مقدم
  • ۰
  • ۰

حتما شما با سری فیبوناچی در دوران دبیرستان آشنا شده‌اید. بطور اختصار سری فیبوناچی از فرمول زیر بدست می‌آید:

f(n) = f(n-1) + f(n-2)

هر عنصر در این سری حاصل جمع دو عنصر ما قبل است. البته مقدار برای n برابر با صفر و یک هر دو یک است. برای نمونه سری زیر، فیبوناچی است:

1,1,2,3,5,8,13,21

تصور کنید می‌خواهید با استفاده از ارلنگ عنصر n یک سری را محاسبه کنید. اگر بخواهیم فرمول ریاضی آورده شده را به یک برنامه ارلنگ تبدیل کنیم کار سختی در پیش نخواهیم داشت.

-module(fib).
-export([fib/1]).

fib(0) -> 0;
fib(1) -> 1;
fib(N) -> fib(N-1) + fib(N-2).

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

1>    c(fib.erl).
{ok,fib}

2>    [fib:fib(1), fib:fib(2), fib:fib(3), fib:fib(4), fib:fib(5), fib:fib(6)].
[1,1,2,3,5,8]

خب بنظر کار ما تمام است. اما تابع تعریف شده مشکلاتی دارد. از آنجایی که برای نمونه (fib(n-1) = fib(n-2) + fib(n-3 پس می توان نتیجه گرفت که مقدار(fib(n-2 دوبار محاسبه می‌شود و این مسئله بار زیادی برروی سیستم می‌گذارد.

راه حال بهتر استفاده از نوع خاصی از توابع بازگشتی است که به آنها tail-recursive گفته می‌شود. شاید بهترین ترجمه برای آن بازگشتی انتهایی باشد. در این توابع باز هم خود تابع صدا زده می‌شود اما به عنوان آخرین عبارت و کنترل هرگز به تابع صدا زننده بر نمی‌گردد. با استفاده از این تعریف تابع بصورت زیر باز تعریف می‌شود:

-module(fib2).
-export([fib/1]).

fib(N) -> fib_iter(N, 0, 1).

fib_iter(0, Result, _Next) -> Result;
fib_iter(Iter, Result, Next) when Iter > 0 -> fib_iter(Iter-1, Next, Result+Next).

برای مقایسه سرعت دو تابع می‌شود از تابع tc در ماژول timer با گونه زیر استفاده کرد. نتیجه برای هر دو تابع قابل مشاهده و مقایسه است.

 

timer:tc(fib, fib, [40]). % {556300, 102334155}

timer:tc(fib2,fib,[40]). %{0, 102334155}

مولفه اول در توپل نشان دهنده زمان اجرا بر مبنای میلی ثانیه است. همانطور که می بینید تفاوت فاحش است.

لازم به ذکر است تا کنون JVM و NET. از بازگشتی انتهایی پشتیبانی نمی‌کنند هرچند بنابه گفته مایکروسافت در سال آینده این قابلیت اضافه خواهد شد.

  • مهدی حسینی مقدم