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

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

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

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

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

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

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

  • ۰
  • ۰

در پستی که خیلی وقت ها پیش منتشر کرده بودم روش های استفاده از VIM را برای محیط ارلنگ توضیح داده بودم. در این مدت تغییراتی زیادی در این خصوص صورت گرفت و پروژه‌ی جدیدی راه اندازی شد و بنابراین تصمیم گرفتم پست جدیدی در این خصوص بنویسم.

برای راه اندازی پلاگین vim-erlang باید مراحل زیر را انجام دهیم:

 $ mkdir -p ~/.vim/bundle
 $ cd ~/.vim/bundle
 $ git clone https://github.com/vim-erlang/vim-erlang-runtime

بعد خط زیر را در فایل vimrc. اضافه کنیم(اگر فایلی به این اسم موجود نیست آن را بسازیم):

:set runtimepath^=~/.vim/bundle/vim-erlang-runtime/

مرحله‌ی بعدی نصب vim-plug است که البته برای سایر پلاگین هم قابل استفاده است، برای نصب آن باید کامندهای زیر را اجرا کنیم:

curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

حالا مجدد فایل vimrc. را باز کرده و عبارت زیر را به آن اضافه می کنیم:

call plug#begin('~/.vim/plugged')

Plug 'vim-erlang/vim-erlang-runtime'

call plug#end()

مرحله آخر اجرای برنامه vim است و پس از آن با استفاده از PlugInstall: پلاگین مربوطه را نصب می‌کنیم. کار تمام است و حالا vim برای استفاده از ارلنگ آماده است. 

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

در این مثال باید برنامه ئی بنویسیم که گذرواژه کاربر رو بررسی کند و بتواند گذرواژه های ساده، خیلی ساده، قوی و خیلی قوی را شناسایی کند. تعاریف بشرح زیر است:

گذرواژه‌ی خیلی ضعیف: متشکل از عدد است و طولی زیر 8 کارکتر دارد

گذرواژه‌ی ضعیف: متشکل از حروف است و طولی زیر 8 کارکتر دارد

گذرواژه‌ی قوی: متشکل از عدد و حروف است و طولی بیش از 8 کارکتر دارد

گذارواژه‌ی خیلی قوی: متشکل از عدد، حروف و کارکترهای ویژه نظیر !@#$%^&* است

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

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
%%@author Mahdi Hosseini Moghaddam <m.hoseini.m@gmail.com>
%%@doc Exercise 25 from "Exercises for programmers" Book
%%@reference from <a href="http://erlang.blog.ir">مثال هایی در ارلنگ</a>,
%% 2018
%%@copyright 2018 by Mahdi Hosseini Moghaddam
%%@version 0.1
-module(ex25).
-export([main/0]).
-compile([debug_info]).
-define(VERY_WEEK, {"^[0-9]{0,8}$","Very Week"}).
-define(WEEK,{"^[a-zA-Z]{0,8}$","Week"}).
-define(STRONG,{"((?=.+[0-9])(?=.+[A-Za-z])){8,}","Strong"}).
-define(VERY_STRONG,{"(?=.+[0-9])(?=.+[A-Za-z])(?=.*[!@#\$%\^&*]).{8,}","Very Strong"}).

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() ->
	{ok,Q} = readItems(),
	case passwordValidator(Q) of 
		{ok, Msg} ->
			io:fwrite("The password ~s is ~s ~n", [Q,Msg]);
		nomatch ->
			io:fwrite("No Macth ~n")
	end.
get_validation() ->
	[?VERY_STRONG, ?STRONG, ?WEEK, ?VERY_WEEK].

	passwordValidator(Str) ->
	passwordValidator(Str,get_validation()).
	
%@ this function Validates Password
passwordValidator(_Str, [] ) ->
	nomatch;
passwordValidator(Str,[{Re, Msg}|T]) ->
	case re:run(Str,Re) of
		{match, _Captured} -> {ok,Msg} ;
		nomatch -> 
		passwordValidator(Str, T)
	end.


%@doc This function reads the user input the returns the result
readItems() ->
	{ok,[P]} = io:fread(
		"Enter the password ","~s"),
	{ok,P}.

من برای بررسی سختی گذرواژه از عبارات با قاعده (Regular Expression) استفاده کردم. ماژول re ارلنگ این کار را برای من می‌کند. قسمت سخت این مثال محاسبه عبارت باقاعده برای هر سختی هست. با استفاده از ماکرو تعریف آنها را در خط 10 تا 13 مشخص کردم. 

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

در این مثال دو رشته از ورودی دریافت می‌شود و سپس بررسی می‌شود که آیا آن دو آناگرام هستند یا نه. دو رشته را در حالی آناگرام می‌گویند که از کارکترهای یکسانی تشکیل شده باشند. برای نمونه note و tone آناگرام هستند. برای حل این مثال از نوع map استفاده کردم که در آن هر کارکتر یک کلید و تعداد تکرارش مقدار آن می‌باشد.

%%@author Mahdi Hosseini Moghaddam <m.hoseini.m@gmail.com>
%%@doc Exercise 24 from "Exercises for programmers" Book
%%@reference from <a href="http://erlang.blog.ir">مثال هایی در ارلنگ</a>,
%% 2018
%%@copyright 2018 by Mahdi Hosseini Moghaddam
%%@version 0.1
-module(ex24).
-export([main/0]).
-compile([debug_info]).

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() ->
	{P,Q} = readItems(),
	A = isAnagram(P,Q),
	if
		A ==true -> 
			io:fwrite("~s and ~s are anagram ~n", [P,Q]);
		true ->
			io:fwrite("~s and ~s are not anagram ~n", [P,Q])
	end.
%@ this function whether two strings have the same characters
isAnagram(W1,W2) ->	
	M1 = count_char(W1),
	M2 = count_char(W2),
	maps:keys(M2) =:= maps:keys(M1).
	
count_char(Str) -> count_char(Str, #{}).
%@ this function put the characters and numbers of their occurrence in a map
count_char([H|T], X) ->
	 case maps:is_key(H,X) of
        false -> count_char(T, maps:put(H,1,X));
        true  -> Count = maps:get(H,X),
                 count_char(T, maps:update(H,Count+1,X))
    end;
count_char([], X) ->
	X.
%@doc This function reads the user input the returns the result	
readItems() ->
	{ok,[P]} = io:fread(
		"Enter the First word: ","~s"),
	{ok,[Q]} = io:fread(
		"Enter the Second word: ","~s"),
	{P,Q}.
  • مهدی حسینی مقدم
  • ۰
  • ۰

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

Enter your username: Mahdi

Enter your password:

Welcome Mahdi!

 

%%@author Mahdi Hosseini Moghaddam <m.hoseini.m@gmail.com>
%%@doc Exercise 15 from "Exercises for programmers" Book
%%@reference from <a href="http://erlang.blog.ir">مثال هایی در ارلنگ</a>,
%% 2018
%%@copyright 2018 by Mahdi Hosseini Moghaddam
%%@version 0.1
-module(ex15).
-export([main/0]).

%@doc This is my main function, It handles the exceptions and runs the program
main() ->
	try run()
	catch
		error:{badkey,_} -> io:format(
			"Your account does not exist~n");
		throw:negativeNumber -> io:format(
			"Please Enter only non-zero positive number~n")
	end.
%@doc This fucntion print out the result, it first calls readData function then check_login to calculate the result
run() ->
	Account = readData(),
	Result = check_login(Account),
	if
		Result == true ->
			io:fwrite("Login Succeed! ~n");
		true ->
			io:fwrite("Login failed! ~n")
	end.
%@doc This function check the account exist it first try to retrieve the account, if the account exist it then compare the hash version of the entered password with the stored one. if the account does not exist it prompt different message 
check_login({User, Passwd}) ->
	Map = accounts(),
	Val=maps:get(User, Map),
	Val == crypto:hash(sha256,Passwd).
accounts() ->
		#{"mahdi" => crypto:hash(sha256,"123456")}.
%@doc This function reads the user input the returns the result
readData() ->
	Usr = string:strip(io:get_line("Enter your username: "),right,$\n),
	io:fwrite("Enter you password:"),
	Passwd = io:get_password(),
	{Usr, Passwd}.
  • مهدی حسینی مقدم
  • ۱
  • ۰

مستند سازی در ارلنگ

مستندسازی از آن قسمت‌هایی است که اغلب مغفول می‌ماند. ارزش مستندسازی شاید ماه ها پس از اتمام پروژه معلوم شود بخصوص وقتی که خود برنامه‌نویس از فهم کد خودش عاجز می‌ماند. کد نویسی تنها زمانی ارزشمند خواهد بود که کد ما برای برنامه نویسان دیگر، از هم تیمی‌هایمان گرفته تا مشتری های نرم افزای، قابل فهم باشد. ساده‌ترین راه برای مستند سازی استفاده از کامنت است. در ارلنگ برای نوشتن کامنت از علامت % استفاده می‌شود. کامنت‌ها در حقیقت عباراتی هستند که توسط کامپایل بنوعی نادیده گرفته می‌شوند و صرفا برای قابل فهم کردن کد نوشته می‌شوند. علامت % تمام خط جلوی خود را تبدیل به کامنت می‌کند و بنابراین اگر نیاز به نوشتن در چند خط دارید باید در ابتدا هر خط از این علامت استفاده کنید. برای نمونه کد زیر در نظر بگیرید، در این کد با استفاده از کامنت توضیحی در مورد یک تابع نوشته شده است.

-module(mydouble).
-export([double/1]).
%This function doubles its input
double(X) -> X * 2.

 

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

%%@author Mahdi Hosseini Moghaddam <hosseins@purdue.edu>
%%@doc Functions calculating triple 
%%@reference from <a href="http://erlang.blog.ir">کتابارلنگ</a>,
%% 2016
%%@copyright 2016 by Mahdi Hosseini Moghaddam
%%@version 0.1

-module(mytriple).
-export([triple/1]).
-import(mydouble,[double/1]).

%%% This function multiplies a number by 3
triple(X) -> X + double(X).

برای ایجاد مستند برای فایل فوق از فرمان زیر استفاده می‌کنیم:

17> edoc:files(["mytriple.erl"], [{dir, "doc"}]).

خروجی در یک پوشه جدید ساخته می‌شود. فایل index.html را با مرورگر خود باز کنید، روی نام تابع mytriple کلیک کنید،تصویری مشابه تصویر زیر خواهید دید:

حتما توجه شما به علامت @ در کامنتها جلب شده است. این علامت در حقیقت توضیحی است به برنامه مستندساز ارلنگ که اطلاعات ذکر شده مربوط به چه قسمتی است. اگر خواستید توضیحی برای تابع خود بنویسید که در مستند نهایی ایجاد شده بیاید کافی است از @doc استفاده کنید. علت خالی بودن توضیح تابع triple در شکل بالا عدم استفاده ما از @doc است. کافیست آن را پس از % اضافه کنیم و دوباره فرمان قبل را اجرا کنیم.

%%@author Mahdi Hosseini Moghaddam <m.hoseini.m@gmail.com>
%%@doc Functions calculating triple 
%%@reference from <a href="http://erlang.blog.ir">کتاب ارلنگ</a>,
%% 2016
%%@copyright 2016 by Mahdi Hosseini Moghaddam
%%@version 0.1
-module(mytriple).
-export([triple/1]).
-import(mydouble,[double/1]).

%@doc This function multiplies a number by 3
triple(X) -> X + double(X).

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

%%@author Mahdi Hosseini Moghaddam <m.hoseini.m@gmail.com>
%%@doc Functions calculating triple 
%%@reference from <a href="http://erlang.blog.ir">کتاب ارلنگ</a>,
%% 2016
%%@copyright 2016 by Mahdi Hosseini Moghaddam
%%@version 0.1
-module(mytriple).
-export([triple/1]).
-import(mydouble,[double/1]).

%@doc This function multiplies a number by 3
-spec(triple(number()) -> number()).
triple(X) -> X + double(X).

شما می توانید برای همه برنامه خود مستند بسازید برای اینکار از فرمان زیر استفاده کنید و خروجی را مشاهده کنید. 

15> edoc:files(["mytriple.erl", mydouble.erl"]). 

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

در این مثال قرار است کارکردن با عبارات شرطی نظیر if تست شود هرچند در ارلنگ در بسیاری از موارد می‌توان از آن صرفنظر کرد و با خود توابع و تطابق الگو به این کارکرد رسید. برای این مثال قرار است مبلغ و اسم ایالت پرسیده شود چنانچه نام ایالت WI بود 5.5% درصد مالیات محاسبه شده و مقدار کل برگردد و در غیر اینصورت همان مبلغ اصلی برگشت داده شود. برای چاپ خروجی از if استفاده کردم اما برای محاسبه با استفاده از تطابق الگو تمایز ایجاد کردم.  در این مثال برای نخستین بار سعی در مستند سازی کد کردم که در پست مربوطه  به آن توضیح مفصل خواهم داد.

%%@author Mahdi Hosseini Moghaddam <m.hoseini.m@gmail.com>
%%@doc Exercise 14 from "Exercises for programmers" Book
%%@reference from <a href="http://erlang.blog.ir">مثال هایی در ارلنگ</a>,
%% 2016
%%@copyright 2018 by Mahdi Hosseini Moghaddam
%%@version 0.1
-module(ex14).
-export([main/0]).
-define(WITAX, 0.055).
%@doc This is my main function, It handles the exceptions and runs the program
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.
%@doc This fucntion print out the result, it first calls readData function then calc_Tax to calculate the result
run() ->
	{A, State} = readData(),
	{At, Taxed} = calc_Tax(A, State),
	if
		State =="WI" ->
		io:fwrite("The subtotal is $~w~n The tax is $~w~n the total is ~.2f~n"
					,[A,Taxed,At]);
		true ->
			io:fwrite("The total is $~w~n",[At])
	end.
%@doc This function calculates the tax
calc_Tax(A,"WI") -> 
	{A * (1+?WITAX),A * ?WITAX};
calc_Tax(A,_) ->
	{A,0}.
	
%@doc This function reads the user input the returns the result, it checks for invalid input like negative numbers
readData() ->
	{ok,[A]} = io:fread(
		"What is the order amount? ","~d"),
	State = string:strip(io:get_line("What's the state? "),right,$\n),
	if
		A =< 0  -> 
			throw(negativeNumber);
		true -> ok
	end,
	{A, State}.
  • مهدی حسینی مقدم
  • ۰
  • ۰

موارد زیر را از کتاب برنامه نویسی ارلنگ نوشته چزارینی استخراج کرده بودم که بنظرم خیلی مفید هستند. 

1-    ماژول ها پایه های برنامه شما هستند، زمانی که ماژولی را می‌سازید سعی کنید تا جای ممکن توابع کمتری را با استفاده از export عمومی کنید. با این کار پیچیدگی کد خود را کنترل می‌کنید.

2-    ماژول های بهم وابسته نباید یک دایره بسازند، به این صورت که ماژول A به B وابسته باشد، B به C  و C به A.

3-    در یک ماژول توابع مربوط بهم را در کنار یکدیگر بنویسید.

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

5-    از کامنت کردن کد‌های قدیمی خودداری کنید، آنها را حذف کنید و در جای دیگری ذخیره کنید. مثلا در یک نسخه پایین تر از کد.

6-    تا جای ممکن از توپل بجای لیست استفاده کنید. سایز توپل در سیستم 32 بیتی 8 بایت بیشتر از مجموع طول اعضای داخل آن است در حالی که لیست ها به ازاء هر عضو 4 بایت فضا اشغال می‌کنند.

7-    توجه داشته باشید که اتم ها توسط آشغال جمع کن سیستم حذف نمی‌شوند و ابدی هستند.

8-    اگر نیاز دارید پیام بزرگی به تعداد زیادی پروسس بفرستید ابتدا آن را به باینری تبدیل کنید. باینری های بزرگتر از 64 بایت با استفاده از نشانگر(Pointer) ارسال می‌شوند.

9-    حتما کد رو مستند کنید.

10- حتما از سیستم نوع (Type system) اختیاری ارلنگ استفاده کنید و با استفاده از Dyalizer قبل از کامپایل کردن صحت کد خود را بررسی کنید

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

این مثال عینا شبیه مثال قبل است با این تفاوت که فرمول محاسبه مبلغ نهایی تفاوت دارد.

A = P (1 + nr )^nt

در این مثال با توجه به اینکه تنها به دو رقم اعشار در جواب نیاز دارم از .2f استفاده کردم که در تابع io:format مشهود است. 

-module(ex13).
-export([main/0]).
-compile([debug_info]).

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() ->
	{P,R,T,N} = readItems(),
	A = calc_Investment({P,R,T,N}),
	io:fwrite(
		"$~w invested at ~w% for ~w years compounded ~w times per year is $~.2f",
		[P,R,T,N,A]).

calc_Investment({P,R,T,N}) -> 
	P*(math:pow(1+R/(N*100),N*T)).
	
readItems() ->
	{ok,[P]} = io:fread(
		"Enter the principal: ","~d"),
	{R,_} = string:to_float(string:strip(
			io:get_line("Enter the rate of interest: "), right, $\n)),
	{ok,[T]} = io:fread(
		"Enter the number of years: ","~d"),
	{ok,[N]} = io:fread(
		"What is the number of times the interest is compounded per year? ","~d"),
	if
		P =< 0 orelse R =< 0 orelse T =< 0 orelse N =< 0 -> 
			throw(negativeNumber);
		true -> ok
	end,
	{P,R,T,N}.
  • مهدی حسینی مقدم
  • ۰
  • ۰

این مثال محاسبه سود است. برای مثال اگر شما 1500 دلار داشته باشید و سالانه 4.3% سود بگیرید بعد از 4 سال پول شما از طریق فرمول زیر به 1758 خواهد رسید.

A = P(1 + rt)

که در آن r همان نرخ سود و t تعداد سال است و P هم همان پول اولیه شماست. در نوشتن این کد به مشکلی برخوردم و آنهم خواندن نوع اعشاری بود که در کد نحوه آن قابل مشاهده است. نکته دیگر استفاده از دیباگر ارلنگ بود. برای این کار باید debug_info را به نحوه زیر به کد اضافه کنید. کار با دیباگر ارلنگ را در زیر کد توضیح خواهد داد.

-module(ex12).
-export([main/0]).
-compile([debug_info]).

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() ->
	{P,I,Y} = readItems(),
	Total = calc_Investment({P,I,Y}),
	io:fwrite(
		"After ~w years at ~w%, the investment will be worth $~w.",
		[Y,I,Total]).

calc_Investment({P,I,Y}) -> 
	P*(1+(I*Y/100)).
	
readItems() ->
	{ok,[P]} = io:fread(
		"Enter the principal: ","~d"),
	%{ok,[I]} = io:fread(
	%	"Enter the rate of interest: ","~f"),
	{I,_} = string:to_float(string:strip(
			io:get_line("Enter the rate of interest: "), right, $\n)),
	{ok,[Y]} = io:fread(
		"Enter the number of years: ","~d"),
	if
		P =< 0 orelse I =< 0 orelse Y =< 0 -> 
			throw(negativeNumber);
		true -> ok
	end,
	{P,I,Y}.

خوب همانطور که مشخص است ابتدا debug_info  را به کد اضافه می‌کنیم. کد را به صورت معمول کامپایل کرده و سپس دیباگر را اجرا می‌کنیم. دیباگر یک محیط گرافیکی دارد که باید ماژول خود را از قسمت Module انتخاب کنیم و سپس تابع اصلی را انتخاب کنیم. می‌توانیم خیلی ساده روی هر خط کد break بگذاریم و خط به خط پیش رویم. کامند ها را بترتیب زیر اجرا می‌کنیم:

163> c(ex12,debug_info).
{ok,ex12}
164> debugger:start().
{ok,<0.552.0>}
  • مهدی حسینی مقدم
  • ۰
  • ۰

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

-module(ex10).
-export([main/0]).
-define(TAX, 0.55).

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() ->
	L = readItems(),
	[Sub,Taxed,Total] = calc_tax(L),
	io:fwrite(
		"Subtotal: $~w~nTax: $~w~nTotal: $~w~n",
		[Sub,Taxed,Total]).

calc_tax(L) -> calc_tax(L,0).
calc_tax([],Total) ->
	[Total, Total*?TAX,(1+?TAX)*Total];
calc_tax([{P,Q}|T],Total) ->
	calc_tax(T,Total+(P*Q)).
	
readItems() ->
	{ok,[P1]} = io:fread(
		"Enter the price of item 1: ","~d"),
	{ok,[Q1]} = io:fread(
		"Enter the quantity of item 1: ","~d"),
	{ok,[P2]} = io:fread(
		"Enter the price of item 2: ","~d"),
	{ok,[Q2]} = io:fread(
		"Enter the quantity of item 2: ","~d"),
	{ok,[P3]} = io:fread(
		"Enter the price of item 3: ","~d"),
	{ok,[Q3]} = io:fread(
		"Enter the quantity of item 3: ","~d"),
	if
		P1 =< 0 orelse Q1 =< 0 orelse P2 =< 0 orelse Q2 =< 0 orelse
		P3 =< 0 orelse Q3 =< 0 -> throw(negativeNumber);
		true -> ok
	end,
	[{P1,Q1},{P2,Q2},{P3,Q3}].
  • مهدی حسینی مقدم