Skip to content

Instantly share code, notes, and snippets.

@shahryarjb
Last active February 10, 2019 08:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shahryarjb/a18f92f5ced8b9503e7002c61f72d52c to your computer and use it in GitHub Desktop.
Save shahryarjb/a18f92f5ced8b9503e7002c61f72d52c to your computer and use it in GitHub Desktop.
نکات مربوط به الکسیر بر اساس تفکیک دستورات

در این قسمت یک تابع ناشناس با چند بدنه درست گردیده

hhh_open = fn                                       
  {:ok, file}  -> "Read data: #{IO.read(file, :line)}"
  {_,   error} -> "Error: #{:file.format_error(error)}"
end

برای فراخوانی به صورت زیر عمل کنید

hhh_open.(File.open("/Applications/MAMP/htdocs/email.php"))

اگر در جواب پترن اوکی برگشت داده شود پیغام خواندن و در صورت مشکل پیغام دوم تقریبا شبی یک شرط می باشد

فانکشن ناشناس می تواند فانکشن ناشناس دیگری را فراخوانی کند

fun1 = fn -> fn -> "Hello" end end
fun1.()
#Function<20.99386804/0 in :erl_eval.expr/5>
fun1.().()
"Hello"

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

مثال

bbbs = fn name -> (fn -> "hello #{name}" end) end
#Function<6.99386804/1 in :erl_eval.expr/5>
bbbs.("Shahryar").()
"hello Shahryar"

در توابع ناشناس می توان به مواردی کاربردی در ماژول های الکسیر پرداخت به صورت مثال یک لیست داریم و می خواهیم تمام اعداد داخل آن را ضرب بر دو کنیم

list = [1, 3, 5, 7, 9]
Enum.map list, fn elem -> elem * 2 end
[2, 6, 10, 14, 18]

یا مثال بیشتر

Enum.map list, fn elem -> elem > 6 end
[false, false, false, true, true]

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

defmodule Greeter do
  def for(name, greeting) do
    fn
      (^name) -> "#{greeting} #{name}"
      (_)     -> "I don't know you"
    end
  end
end

mr_valim = Greeter.for("José", "Oi!")

IO.puts mr_valim.("José")
"Oi! José"
IO.puts mr_valim.("Dave")
"I don't know you"

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

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"]

توابع الکسیر می توانند رفتاری شگفت انگیری را برای شما به نمایش بزارند . به صورت مثال شما دو تابع زیر را درست بکنید

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

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

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

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

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

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

iex(1)> for x <- ~w{ cat dog }, into: %{}, do: { x, String.upcase(x) }
%{"cat" => "CAT", "dog" => "DOG"}
iex(2)> for x <- ~w{ cat dog }, into: Map.new, do: { x, String.upcase(x) }
%{"cat" => "CAT", "dog" => "DOG"}
iex(3)> for x <- ~w{ cat dog }, into: %{"ant" => "ANT"}, do: { x, String.upcase(x) }
%{"ant" => "ANT", "cat" => "CAT", "dog" => "DOG"}

همانطور که می بنید ما از into در ماکروی خودمون استفاده کردیم و به چند صورت نیز پیاده سازی شد . یک بار با یک مپ خالی و دومین بار با ساخت یک مپ جدید و سومین بار با یک پارامتر پیشفرض و مشخص کردن نوع

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

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) when is_integer(n) and n > 0 do 
    n * of(n-1) 
  end
end

در گارد شما فقط می توانید موارد محدودیتی از عملیات را اجرا کنید

  • موارد اجرایی
==, !=, ===, !==, >, <, <=, >=

مواردی مثلا || و && نمی تواند موارد استفاده قرار بگیرد

در کل خیلی مواردی که جواب شرطی دارند جزو این مورد می شند مخصوصا مواردی که پاسخ مثل بولین دارند.

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)

توجه: موارد بالا جواب هایی مثل نه درست دارند یعنی همان false به همین ترتیب می توانند شامل این بخش شوند

نکته: بر اساس بررسی های قبلی که انجام دادم برای ساخت گارد های سفارشی نیاز به نوشتن ماکرو می باشد به همین ترتیب شما باید بدانید در ماکرو شما نمی توانید خیلی از ماژول های الکسیر استفاده کنید. که این مورد بسیار اذیت کننده می باشد


برای اطلاعات بیشتر لینک زیر را مشاهده کنید که پرسش من در همین باره در انجمن الکسیرمی باشد

https://elixirforum.com/t/cannot-invoke-remote-function-regex-match-2-inside-guard/16338

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

https://hexdocs.pm/elixir/guards.html


در خیلی از آموزش ها در مورد مواردی مثل tail آموزش های زیادی داده شده و همینطور از ویژگی جادویی اون صحبت شده در این قطعه کد ما می خوایم یک الگوریتمی رو بنویسیم که لیست رو شمارش کنه ولی به کمک tail / head برای مثال

defmodule MyList do
  def len([]), do: 0 
  def len([head|tail]), do: 1 + len(tail)
end

حال کد بالا چطور اجرا می شود ؟

len([11,12,13,14,15])
 	= 1 + len([12,13,14,15])
 	= 1 + 1 + len([13,14,15])
 	= 1 + 1 + 1 + len([14,15])
 	= 1 + 1 + 1 + 1 + len([15])
 	= 1 + 1 + 1 + 1 + 1 + len([])
 	= 1 + 1 + 1 + 1 + 1 + 0
 	= 5

توابع ناشناس رو فبلا توضیح دادیم حال اگر بخواهیم با یک لیست تلفیق کنیم کمی جالب می شود به صورت مثال

MyList.map [1,2,3,4], fn (n) -> n+1 end

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

اضافه کردن مقداری به یک لیست

iex> [ 1, 2, 3 | [ 4, 5, 6 ]]
[1, 2, 3, 4, 5, 6]

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

داده ها به صورت زیر می باشد

defmodule WeatherHistory do

  def test_data do
    [
     [1366225622, 26, 15, 0.125],
     [1366225622, 27, 15, 0.45],
     [1366225622, 28, 21, 0.25],
     [1366229222, 26, 19, 0.081],
     [1366229222, 27, 17, 0.468],
     [1366229222, 28, 15, 0.60],
     [1366232822, 26, 22, 0.095],
     [1366232822, 27, 21, 0.05],
     [1366232822, 28, 24, 0.03],
     [1366236422, 26, 17, 0.025]
    ]
  end

اگر بخواهیم توضیح بدهیم اولین پرامتر هر لیست زمانش دومی مثلا درجه مورد نظر ما و ... می باشد حال ما از دومین ورودی لیست عدد ۲۷ را نیاز داریم

def for_location_27([]), do: []
def for_location_27([ [time, 27, temp, rain ] | tail]) do
  [ [time, 27, temp, rain] | for_location_27(tail) ]
end
def for_location_27([ _ | tail]), do: for_location_27(tail)

end

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

به صورت مثال فراخوانی می کنیم

for_location_27(test_data)
 	[[1366225622, 27, 15, 0.45], [1366229222, 27, 17, 0.468], [1366232822, 27, 21, 0.05]]

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

ماژول لیست دستورات بسیار کلیدی رو به همراه خودش داره که می تونه به کدهای شما قدرت بسیار زیادی رو بده به صورت مثالی برخی از این مورد به شرح زیر می باشد

	#
 	# Concatenate lists
 	#
 	iex> [1,2,3] ++ [4,5,6]
 	[1, 2, 3, 4, 5, 6]
 	#
 	# Flatten
 	#
 	iex> List.flatten([[[1], 2], [[[3]]]])
 	[1, 2, 3]
 	#
 	# Folding (like reduce, but can choose direction)
 	#
 	iex> List.foldl([1,2,3], "", fn value, acc -> "#{value}(#{acc})" end)
 	"3(2(1()))"
 	iex> List.foldr([1,2,3], "", fn value, acc -> "#{value}(#{acc})" end)
 	"1(2(3()))"
 	#
 	# Updating in the middle (not a cheap operation)
 	#
 	iex> list = [ 1, 2, 3 ]
 	[ 1, 2, 3 ]
 	iex> List.replace_at(list, 2, "buckle my shoe")
 	[1, 2, "buckle my shoe"]
 	#
 	# Accessing tuples within lists
 	#
 	iex> kw = [{:name, "Dave"}, {:likes, "Programming"}, {:where, "Dallas", "TX"}]
 	[{:name, "Dave"}, {:likes, "Programming"}, {:where, "Dallas", "TX"}]
 	iex> List.keyfind(kw, "Dallas", 1)
 	{:where, "Dallas", "TX"}
 	iex> List.keyfind(kw, "TX", 2)
 	{:where, "Dallas", "TX"}
 	iex> List.keyfind(kw, "TX", 1)
 	nil
 	iex> List.keyfind(kw, "TX", 1, "No city called TX")
 	"No city called TX"
 	iex> kw = List.keydelete(kw, "TX", 2)
 	[name: "Dave", likes: "Programming"]
 	iex> kw = List.keyreplace(kw, :name, 0, {:first_name, "Dave"})
 	[first_name: "Dave", likes: "Programming"]

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

people = [
  %{ name: "Grumpy",    height: 1.24 },
  %{ name: "Dave",      height: 1.88 },
  %{ name: "Dopey",     height: 1.32 },
  %{ name: "Shaquille", height: 2.16 },
  %{ name: "Sneezy",    height: 1.28 }
]

IO.inspect(for person = %{ height: height } <- people, height > 1.5, do: person)

خروجی کد بالا به شرح زیر می باشد

[%{height: 1.88, name: "Dave"}, %{height: 2.16, name: "Shaquille"}]

یک راه دیگراستفاده از چند فانکشن به وسیله گارد و همینطور تابع ناشناس می باشد به صورت مثال توابع و گارد مربوط به آن به شرح زیر می باشد

defmodule HotelRoom do

  def book(%{name: name, height: height})
  when height > 1.9 do
    IO.puts "Need extra-long bed for #{name}"
  end

  def book(%{name: name, height: height})
  when height < 1.3 do
    IO.puts "Need low shower controls for #{name}"
  end

  def book(person) do
    IO.puts "Need regular bed for #{person.name}"
  end

end

حال می توان به صورت زیر اطلاعات مربوط به یک مپ در لیست را به آن داد

people 
  |> Enum.each(&HotelRoom.book/1)

#=> Need low shower controls for Grumpy
#   Need regular bed for Dave
#   Need regular bed for Dopey
#   Need extra-long bed for Shaquille
#   Need low shower controls for Sneezy

ماژول Enum.each به صورت خلاصه هر داده را به ترتیب وارد تابع ما به وسیله نوع خلاصه از تابع ناشناس می کند برای اطلاعات بیشتر شما می توانید در ترمینال iex خود h Enum.each را تایپ کنید و یک مثال از خلاصه آن به شرح زیر می باشد

Enum.each(["some", "example"], fn(x) -> IO.puts x end)
    "some"
    "example"
    #=> :ok

یکی از ساده ترین راه های به روز رسانی مپ

new_map = %{ old_map | key => value,}

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

cast = %{
  buttercup: %{
    actor:    {"Robin", "Wright"},
    role:      "princess"
  },
  westley: %{
    actor:    {"Carey", "Elwes"},
    role:      "farm boy"
  }
}

IO.inspect get_in(cast, [Access.key(:westley), :actor, Access.elem(1)])
#=> "Elwes"
```
“~C
A character list with no escaping or interpolation
~c
A character list, escaped and interpolated just like a single-quoted string
~D
A Date in the format yyyy-mm-dd
~N
A naive (raw) DateTime in the format yyyy-mm-dd hh:mm:ss[.ddd]
~R
A regular expression with no escaping or interpolation
~r
A regular expression, escaped and interpolated
~S
A string with no escaping or interpolation
~s
A string, escaped and interpolated just like a double-quoted string
~T
A Time in the format hh:mm:ss[.dddd]
~W
A list of whitespace-delimited words, with no escaping or interpolation
~w
A list of whitespace-delimited words, with escaping and interpolation”
```

اگر بخوام یک تعریف خود ساخته از استریم بگم این است که استریم منابع شما رو به موقع و با تفکیک و بجا استفاده می کنه حالا وقتی شما از یک ماژول مثل Enum استفاده کنید. اون بدون ملاحظه سعی می کنه تا حصول جواب هرچی دارید رو ببلعه . البته الکسیر خودش برای بهینه سازی هایی نوشته و شما نگران نباشید. ما وقتی از استریم استفاده می کنیم جواب رو باز باید در آخر با enum نشون بدیم چون خود استریم جواب بر نمی گردونه به صورت مثال شما بزنید

Stream.map(1..10_000_000, &(&1+1))
#Stream<[enum: 1..10000000, funs: [#Function<48.58052446/1 in Stream.map/2>]]>

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

|> Enum.take(5)

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

یک مپ سنگین می خوایم بسازیم اول با انیوم

iex(8)> Enum.map(1..10_000_000, &(&1+1)) |> Enum.take(5)
[2, 3, 4, 5, 6]

اگر این کد بزنید متعاد ۸ ثانیه طول می کشد تا به شما نشان بدهد و صبر می کند تمام جواب ها وقتی کامل شد تازه به شما نشان دهد ولی استریم منتظر نمی ماند تا جواب کامل شود سرعت را تجربه کنید

iex(10)> Stream.map(1..10_000_000, &(&1+1)) |> Enum.take(5)
[2, 3, 4, 5, 6]

حال شما سرعت و مدیریت حافظه را به صورت شفاف دیدید.

نکته: لازم به ذکر است بگویم در این مثال جریان محدود می باشد ولی شما می توانید به کمک استریم به صورت همیشگی یک چیزی را به جریان بیندازید


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

Stream.cycle(~w{ green white }) 
|> Stream.zip(1..5) 
|> Enum.map(fn {class, value} -> "<tr class='#{class}'><td>#{value}</td></tr>\n" end) 
|> IO.puts

پاسخ دریافتی

<tr class='green'><td>1</td></tr>
<tr class='white'><td>2</td></tr>
<tr class='green'><td>3</td></tr>
<tr class='white'><td>4</td></tr>
<tr class='green'><td>5</td></tr>

:ok

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

cycle
repeatedly
iterate
unfold
resource

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

Stream.repeatedly(fn -> true end) |> Enum.take(3)
[true, true, true]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment