شروع elixir : آشنایی با ماژول ها و فانکشن ها

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

#آشنایی و آموزش سریع الکسیر

##ماژول ها و فانکشن ها

وقتی برنامه شما بیشتر از یک الی دو خط می شود حال نیاز به این هست که آن ها را دسته بندی کنید . الکسیر نیز در این راه حمایت کننده شما می باشد.
شما وقتی در حال کد زدن می باشید در حقیقت در حال شکست کد های خود در توابع متعدد می باشید ولی اگر بخواهید این توابع را دسته بندی کنید نیاز به ساختاری به نام module دارید . در حقیقت ساختار الکسیر به نحوی هست که شما باید این چنین کاری را انجام بدهید .

توجه : تا به حال ما سعی کردیم برای تست در کنسول به وسیله iex تمرین کنیم ولی از این به بعد به سمت ساخت فایل هم خواهیم رفت .

توجه : قبل از هر کاری لطفا یک فایل به هر نامی ولی با پسوند exs در یک مسیری بسازید و بعد از آن با ترمینال به آن مسیر بروید و به صورت زیر آن را اجرا کنید .

توجه : نام فایل من : times.exs

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

defmodule Times do
  def double(n) do
    n * 2
  end
end

و حال دستور زیر را بزنید برای بالا آوردن فایل خودتان

iex times.exs 

حال فایل شما لود شده و شما در ترمینال می توانید به صورت زیر اقدام کنید :

iex> Times.double(4)

اگر دستور بالا را لود کنید باید برای شما جواب ۸ چاپ گردد

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

iex> c "times.exs"

حال اگر شما بجای یک عدد یک استرینگ قرار بدهید چه اتفاقی می افتد ؟

iex> Times.double("ss")

به شما ارور زیر را نشان می دهد

** (ArithmeticError) bad argument in arithmetic expression
    times.exs:7: Times.double/1

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

بیاییم یک بار دیگه ببنیم چرا اینطور ارور می دهد ؟

در الکسیر زمانی که یک فانکشن ارور دریافت می کند علاوه بر اسم آن یک عدد نیز چاپ می کند و مورد آن معنی هست که شما دارای یک آرگومنت هستید و اگر شما بیایید ۳ آرگومنت در آن قرار بدهید عدد دریافتی شما تغییر خواهد کرد.

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

###بدنه فانکشن یک بلاک می باشد

فانکشن ها def — end تنها راه برای دسته بندی کردن و گروه کردن کد ها می باشد که می توان آن را در دیگر فانکشن ها یا در دیگر فانکشن های یک ماژول دیگر فراخوانی کرد

نکته : def — end همیشه تنها سینتکز در elixir نیستند شما می توانید کد های خودتان را به صورت زیر بنویسید

iex> def rogers(), do: "hi shahryar"

و یا می توانید به صورت زیر انجام بدهید :

def grr(grr , name), do: (
        IO.puts grr
        IO.puts("my name is #{name}")
  )

نکته : شما روش های دیگری نیز می توانید استفاده کنید ولی باید در نظر داشته باشید بیشتر مردم در الکسیر برای راحتی کار و خوانایی استفاده از def — end راترجی می دهند به همین ترتیب استفاده نوع دیگر از نوشتن هیچ چیز جدید به شما اضافه نخواهد کرد فقط کمی از استاندارد های زیبا نویسی دور شدید بهتر هست همه برای توسعه کد ها از روند معمول و خوانا تر استفاده کنیم

تمرین های بیشتر :



من کد های این بخش را در لینک زیر نیز قرار دادم :

###صدا زدن فانکشن و پترن ماچین

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

توجه : فایل تمرینی که در این پست قرار می دهیم و در مورد آن بحث می کنیم به نام factorial.exs می باشد

توجه : نام ماژول همیشه باید با حروف بزرگ شروع شود

به کد زیر توجه کنید :

defmodule Factorial do
        def of(0), do: 1
        def of(n), do: n * of(n - 1)
end

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

بیایید تست کنیم :

iex> Factorial.of(3)
6

توجه : اگر بجای عدد بالا 0 قرار بدهم بجای 6 به من 1 بر می گرداند

اما چطور این مورد کار می کند :

وقتی شما به الکسیر عدد 2 می دهید اول سعی می کند به اولین تابع برود و خودش را مورد ارزیابی قرار بدهد و وقتی می بیند یک سان نمی باشد جواب fails می شود و بعد به تابع بعدی می رود و بعد از ارزیابی و درست بودن true و وارد بدنه شده و سر آخر جواب مورد نظر برگشت داده می شود

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

توجه : دوباره به کد های بالا توجه کنید و جای فانکشن دوم با اول را تغییر دهید ؟ به نظر شما چه مشکلی به وجود می آید ؟ بله همیشه شرط و مچ بودن پا برجسات و شما یک کد بد و بدون منطقی را نوشتید و حتی در موقع کامپایل مجدد به شما یک ارور نیز نمایش می دهد

defmodule Factorial do
        def of(n), do: n * of(n - 1)
        def of(0), do: 1
end

ارور :

warning: this clause cannot match because a previous clause at line 2 always matches
  factorial.exs:3

کد های این بخش :

###نگهبان شرط

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

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

به کد زیر توجه کنید :

defmodule Guard do
  def what_is(x) when is_number(x) do
    IO.puts "#{x} is a number"
  end
  def what_is(x) when is_list(x) do
    IO.puts "#{inspect(x)} is a list"
  end
  def what_is(x) when is_atom(x) do
    IO.puts "#{x} is an atom"
  end
end

Guard.what_is(99)        # => 99 is a number
Guard.what_is(:cat)      # => cat is an atom
Guard.what_is([1,2,3])   # => [1,2,3] is a list

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

defmodule Factorial do
        def of(0), do: 1
        def of(n), do: n * of(n - 1)
end

به نظر شما اگر من یک عدد منفی قرار بدهم فانکشن بالای من جواب درست بر می گرداند ؟ خیر چون هیچ شرطی برای آن تعبیه نشده است.

کد تصحیح شده :

defmodule Factorial do
  def of(0), do: 1
  def of(n) when n > 0 do
    n * of(n-1)
  end
end

دیگر امکان وارد کردن عدد منفی در قطعه کد بالا امکان پذیر نیست .

###اعمال محدودیت ها

خوب خیلی از موارد در بالا برای when مطرح شد و در ادامه بیشتر به سمت اعمال محدودیت ها یا مواردی که ایجاد کننده محدودیت می باشد می رویم

استفاده از اپراتور ها

==, !=, ===, !==, >, <, <=, >=

استفاده از بولین :

or, and, not, ! , || , &&

استفاده از عملگر های حسابی

+, -, *, /

####استفاده از عملگر های اضافه کننده یا کم کننده ( لیست به لیست - باینری به باینری و … )

<> و ++

####استفاده از in برای پیدا کردن رنج و بدون یا نبودن ( چک کردن یک ارزش در دامنه ی یک کالکشن یا رنجش )

####استفاده از چک کنند نوع فانکشن

این مورد در erlang درست شده و در صورتی برای شما آرگمنت را بر می گرداند که نوع فانکشن درست باشد این مورد را می توانید از اسناد آنلاین پیدا کنید

نمونه :

is_atom is_binary is_bitstring is_boolean is_exception is_float is_function is_integer is_list is_map is_number is_pid is_port is_record is_reference is_tuple

####فانکشن های دیگر

در این نوع قانکشن چیزی به عنوان درست یا غلط بر نمی گردد

abs(number) bit_size(bitstring) byte_size(bitstring) div(number,number) elem(tuple, n) float(term) hd(list) length(list) node() node(pid|ref|port) rem(number,number) round(number) self() tl(list) trunc(number) tuple_size(tuple)

کد های این بخش :

###فانکشن دیفالت

به این فکر کردید که ممکن هست برای از آرگومنت های شما چطور می توانند مقدار دیفالت داشته باشند ؟
شاید خیلی از مواردی که شما فکر می کنید در کد هایتان قرار داده باشید و اعتبار سنجی هم کرده باشید ولی ممکن هست در آن پروژه یا مثال آن مورد به وسیله کاربر درج نگردد برای این کار باید چیکار کرد ؟ چطور می توان یک مورد دیفالت قرار داد :

فقط کافیست شما param \ value در جای آرگومنت خود قرار دهید به قطعه کد زیر توجه کنید

defmodule Example do
  def func(p1, p2 \\ 2, p3 \\ 3, p4) do
    IO.inspect [p1, p2, p3, p4]
  end
end

Example.func("a", "b")             # => ["a",2,3,"b"]
Example.func("a", "b", "c")        # => ["a","b",3,"c"]
Example.func("a", "b", "c", "d")   # => ["a","b","c","d"]

همانطور که می بینید بجای برخی از آرگومنت ها مورد دیفالت قرار گرفته هست و هر کدام نیز با مثال برای شما به نمایش در آمده است

حال بیاییم یک تحلیل ساده نسبت به کد های بالا داشته باشیم .

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

به کد زیر توجه کنید :

defmodule DefaultParams1 do

  def func(p1, p2 \\ 123) do
    IO.inspect [p1, p2]
  end

  def func(p1, 99) do
    IO.puts "you said 99"
  end

end

حال برای درک بهتر بیاییم یک مثال بد هم بزنیم :

def func(p1, p2 \\ 2, p3 \\ 3, p4) do
 IO.inspect [p1, p2, p3, p4]
end

def func(p1, p2) do
 IO.inspect [p1, p2]
end

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

** (CompileError) default_params.exs:7: def func/2 conflicts with defaults from def func/4

دریافت خواهید کرد .

باید توجه داشته باشید شما می توانید اتم هم استفاده کنید

مثال :

def foo(a, b \\ :default) do
        IO.puts "#{a} - #{b}"
end

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

###Private Functions در الکسیر

ما از تعریف اینکه Private Function چیست و چطور باید استفاده بکنیم می گذریم . ولی به این نکته بسنده می کنیم اگر شما یک فانکشن را به صورت پابلیک بیان کردید و از آن چندین نوع تعریف کردید و یکی از آن نوع ها پرویت (خصوصی ) باشد کد شما ولید و درست نمی باشد. Private Function با defp شروع می شود از نظر سینتکز

نکته : حداقل های یک زبان برنامه نویسی برای یادگیری الکسیر نیاز می باشد مثل استفاده از یک فانکشن پرویت و …

###اپراتور شگفت انگیر Pipe

یکی از موارد بسیار ویژه زبان الکسیر اپراتور Pipe می باشد . برای اینکه به درک مناسبی برسید دقیقا وظیفه این اپراتور چیست به کد های زیر توجه کنید :

people = DB.find_customers
orders = Orders.for_customers(people)
tax    = sales_tax(orders, 2016)
filing = prepare_filing(tax)

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

به کد زیر توجه کنید :

filing = DB.find_customers
        |> Orders.for_customers
        |> sales_tax(2016)
        |> prepare_filing

اگر به کد بالا و کد بالاتر توجه کنید دقیقا هردو یک کار را انجام می دهند ولی دومین روش بسیار زیبا تر و تمیز تر می باشد.

برای نمایش زیبایی این بخش یک شبهه کد دیگر را برای شما مثال می زنم :

(1..10) |> Enum.map(&(&1*&1)) |> Enum.filter(&(&1 < 40))

###ماژول در ماژول

به شخصه اینطور نوشتن را در دسته بندی کثیف نویسی قرار می دهم . ولی خوب از امکانات الکسیر می باشد و برای نوشتن کد های بهتر باید نسبت به آن آشنایی داشت

به کد زیر توجه کنید :

defmodule Example do
  def func(p1, p2 \\ 2, p3 \\ 3, p4) do
    IO.inspect [p1, p2, p3, p4]
  end

  def foo(a, b \\ :default) do
  IO.puts "#{a} - #{b}"
  end

  defmodule Yasis do
  def iner_bloc(n) do
  IO.puts("#{n * 2}")
  end
  end

اگر ببنید من یک ماژول دیگر در آن نوشتم و همینطور زیر مجموعه آن یک فانکشن نیز درست کردم حال برای فراخوانی کافیست کد زیر را بنویسید

Example.Yasis.iner_bloc(3)

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

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

مثال بیشتر :

defmodule Mix.Tasks.Doctest do
def run do
   IO.puts("hi , Shahryar")
 end
end
Mix.Tasks.Doctest.run

نکته : این مورد می تواند نمایش دهنده این باشد هیچ ارتباطی با فراخوانی مستقیم و مخلوط ماژول ها می تواند نباشد . ( امکان استفاده در هم و همینطور مستقیم )

###ایمپورت کردن ماژول در ماژول دیگر

مگر می شود بدون import یک پروژه بزرگ را طراحی و ساخت ؟ خوب این کار را تقریبا با کد های بالا می شود فهمید ولی دوباره یک مثال ساده آن را تشریح خواهیم کرد

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

توجه : در صورتی که نمی خواهید از یک فایل دیگر ایمپورت کنید می توانید آخرین end که می بنید یک ماژول جدید بسازید

نحوه خواندن :

Factorial.lasttest

شما می توانید به صورت دیگر نیز ماژول مورد نظر را import کنید :

import List, only: [flatten: 1]
flatten [5,[6,7],8]

در فایل بالا ما فقط یک فانکشن را مجاز به فراخوانی کردیم

نمونه :

import List, only: [ flatten: 1, duplicate: 2 ]

برای اطلاعات بیشتر در مورد ایمپورت پیشنهاد می کنم مثال های خوب لینک زیر را مطالعه کنید

###نام مستعار :

شاید برای شما سخت باشد بنویسید مثلا X.Y.Z.defname و هر دفعه این خط را در کد های خودتان بر اساس نیاز تکرار کنید . خوب اینجا به نظرم شما به alias نیاز دارید

alias Math.List, as: List

به کد بالا نگاه کنید شما ماژول math.List را نام مستعار کردید به List از این به بعد به سادگی می توانید هرجا در کد های خودتان فقط List را صدا بزنید .

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

require Integer

اطلاعات بیشتر :

فراداده داده ها در ماژول

شما می توانید هر هر ماژول فراداده تعریف کنید و آن را با @ اسم بدهید و هرجا که نیاز داشتید آن را فراخوانی و به آن ارزش جدید بدهید . تقریبا مثل یک متغییر عمومی می ماند.

مثال :

defmodule Example do
  @attr "one"
  def first, do: @attr
  @attr "two"
  def second, do: @attr
end

IO.puts "#{Example.second}  #{Example.first}"    # => two  one

###فراخوانی از erlang

همانطور که می دانید elixir از erlang درسته شده است و به همین دلیل می تواند متد های erlang را در خود اجرا کند فقط کافیست با : شروع کنید به صورت مثال :

iex> :io.format("the number is ~3.1f~n", [5.678])

یا :

iex> :egd.create(250, 250)

بدون اینکه نیاز باشد چیزی را ایمپورت کنید یا تغییر دهید. این امکان بسیار جذاب هست احساس قدرت به برنامه نویس می دهد که از ۱۰ سال تجربه erlang نیز استفاده نماید.

قبل از اتمام این بخش باید به دوستان این نکته را اشاره کنم که کتابخانه های زیادی برای الکسیر و erlang درست شده است و به صورت رایگان برای شما قرار گرفته فقط کافیست به سایت


سر بزنید و لذت ببرید .

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

اگر نیاز دارید فایل pdf این بخش آموزش را دانلود کنید از لینک زیر اقدام کنید

3 پسندیده