Do you want to pick up from where you left of?
Take me there

ฟังก์ชัน(Functions)

ใน Elixir และ functional language ตัวอื่นๆ ฟังก์ชันมีความสำคัญอันดับแรกๆ ในบทเรียนนี้จะเรียนรู้เกี่ยวกับประเภทของฟังก์ชั่นใน Elixir ว่าแตกต่างกันอย่างไร?และวิธีการใช้

ฟังก์ชันไม่ระบุตัวตน (Anonymous Function)

เช่นเดียวกับชื่อของมัน Anonymous Function ไม่มีชื่อ ดังนั้นเราเห็นในบทเรียน Enum เหล่านี้มักจะถูกส่งผ่านไปยังฟังก์ชันอื่น ๆ ในการกำหนด Anonymous Function ใน Elixir เราต้องใช้ fn และendเป็นคำหลัก ภายในเหล่านี้เราสามารถกำหนดจำนวน parameter และฟังก์ชันแยกออกจากกันด้วย ->

ลองดูตัวอย่างเบื้องต้น:

iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5

การเขียนย่อด้วยสัญลักษณ์ &

การใช้ Anonymous Functions ใน Elixir สามารถเขียนเพื่อย่อ ได้ตามตัวอย่าง :

iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5

เราอาจคาดเดาได้แล้ว ในรูปแบบนี้ เราสามารถสร้าง parameter ของตัวเองได้เช่น “& 1”, “& 2”, “& 3” เป็นต้น

การจับคู่รูปแบบ (Pattern Matching)

Pattern matching ไม่จำกัดเฉพาะตัวแปรใน Elixir มันสามารถใช้กับลายเซ็นต์ฟังก์ชันที่เราจะเห็นในส่วนนี้

Elixir ใช้ pattern matching เพื่อระบุชุดแรกของ parameter ที่ตรงกับและเรียกใช้ตัวที่เกี่ยวข้อง:

iex> handle_result = fn
...>   {:ok, result} -> IO.puts "Handling result..."
...>   {:error} -> IO.puts "An error has occurred!"
...> end

iex> some_result = 1
1
iex> handle_result.({:ok, some_result})
Handling result...
:ok
iex> handle_result.({:error})
An error has occurred!

ฟังก์ชันที่มีชื่อ (Named Function)

เราสามารถกำหนดฟังก์ชันที่มีชื่อเพื่อให้เราสามารถอ้างอิงได้ในภายหลัง Named Function จะถูกกำหนดไว้ภายในโมดูลโดยใช้คำสั่งdef เราจะเรียนรู้เพิ่มเติมเกี่ยวกับ Modules ในบทเรียนต่อไป เพราะตอนนี้เรามุ่งเน้นไปที่ Named Function เท่านั้น

ฟังก์ชั่นที่กำหนดไว้ภายในโมดูลมีให้ใช้กับโมดูลอื่น ๆ สำหรับการใช้งาน โดยเป็นประโยชน์โดยเฉพาะการสร้าง block ใน Elixir:

defmodule Greeter do
  def hello(name) do
    "Hello, " <> name
  end
end

iex> Greeter.hello("Sean")
"Hello, Sean"

ถ้าฟังก์ชันทั้งหมดครอบคลุมในบรรทัดเดียว เราสามารถย่อลงได้อีกด้วยคำสั้งdo::

defmodule Greeter do
  def hello(name), do: "Hello, " <> name
end

คุณมีความรู้เกี่ยวกับ pattern matching อย่างคราวๆ แล้ว ลองใช้การเรียกซ้ำของ named function:

defmodule Length do
  def of([]), do: 0
  def of([_ | tail]), do: 1 + of(tail)
end

iex> Length.of []
0
iex> Length.of [1, 2, 3]
3

การตั้งชื่อฟังก์ชันและ Arity

เราได้กล่าวไว้ก่อนหน้านี้ว่าฟังก์ชันจะถูกตั้งชื่อโดยการรวมกันของชื่อและ arity (จำนวน argument) ซึ่งหมายความว่าคุณสามารถทำสิ่งต่างๆเช่นนี้:

defmodule Greeter2 do
  def hello(), do: "Hello, anonymous person!"   # hello/0
  def hello(name), do: "Hello, " <> name        # hello/1
  def hello(name1, name2), do: "Hello, #{name1} and #{name2}"
                                                # hello/2
end

iex> Greeter2.hello()
"Hello, anonymous person!"
iex> Greeter2.hello("Fred")
"Hello, Fred"
iex> Greeter2.hello("Fred", "Jane")
"Hello, Fred and Jane"

เราระบุ function names ไว้บน comment แล้ว ครั้งแรกมีการดำเนินการโดยไม่มี argument หรือที่รู้กันคือhello/0;ครั้งที่สองมีการดำเนินการโดยใส่ 1 argument โดยรู้กันคือ hello/1และซึ่่งๆแตกต่างจากฟังก์ชันทำให้เกิด overloads ในภาษาอื่นบางภาษา เหล่านี้เป็นความคิดของ different functions จากแต่ละอื่น ๆ (Pattern matching, อธิบายเพียงไม่กี่นาทีที่ผ่านมา, ใช้เฉพาะเมื่อมีคำจำกัดความหลายคำสำหรับนิยามฟังก์ชันด้วยจำนวน same ของอาร์กิวเมนต์)

ฟังก์ชันเฉพาะตัว (Private Function)

เมื่อเราไม่ต้องการให้โมดูลอื่น ๆ เข้าถึงฟังก์ชันที่เฉพาะเจาะจง เราสามารถทำ Private Functions ได้ Private function สามารถเรียกได้จากภายในโมดูลเท่านั้น เรากำหนดมันใน Elixir ด้วยคำสั้ง defp:

defmodule Greeter do
  def hello(name), do: phrase <> name
  defp phrase, do: "Hello, "
end

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.phrase
** (UndefinedFunctionError) function Greeter.phrase/0 is undefined or private
    Greeter.phrase()

Guard

เราเรียนอย่างสั้นเรื่อง guards ในบทเรียน Control Structures, ตอนนี้เราจะดูว่าเราสามารถนำไปใช้กับ named function ได้อย่างไร เมื่อ Elixir ได้จับคู่กับฟังก์ชั่นใด ๆ แล้ว Guard ที่มีอยู่จะได้รับการทดสอบ.

ในตัวอย่างต่อไปนี้เรามีสองฟังก์ชันที่มีลายเซ็นเดียวกันเราต้องพึ่งพา Guards เพื่อระบุว่าจะใช้ชนิดของ argument ตามที่ได้:

defmodule Greeter do
  def hello(names) when is_list(names) do
    names = Enum.join(names, ", ")
    
    hello(names)
  end

  def hello(name) when is_binary(name) do
    phrase() <> name
  end

  defp phrase, do: "Hello, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

Default Argument

ถ้าเราต้องการค่า Default สำหรับ argument เราใช้ argument \\ value syntax:

defmodule Greeter do
  def hello(name, language_code \\ "en") do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello("Sean", "en")
"Hello, Sean"

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.hello("Sean", "es")
"Hola, Sean"

เมื่อเรารวมตัวอย่าง Guards ของเรากับ argument เริ่มต้นเราจะเจอปัญหา ลองดูสิ่งที่อาจมีลักษณะดังนี้:

defmodule Greeter do
  def hello(names, language_code \\ "en") when is_list(names) do
    names = Enum.join(names, ", ")
    
    hello(names, language_code)
  end

  def hello(name, language_code \\ "en") when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

** (CompileError) iex:31: definitions with multiple clauses and default values require a header. Instead of:

    def foo(:first_clause, b \\ :default) do ... end
    def foo(:second_clause, b) do ... end

เขียนได้อีกแบบ:

    def foo(a, b \\ :default)
    def foo(:first_clause, b) do ... end
    def foo(:second_clause, b) do ... end

def hello/2 has multiple clauses and defines defaults in one or more clauses
    iex:31: (module)

Elixir ไม่สามารถ argument เริ่มต้นในฟังก์ชั่นการจับคู่หลายอันอาจทำให้เกิดความสับสน ในการจัดการนี้เราเพิ่มหัวฟังก์ชันด้วย argument เริ่มต้นของเรา:

defmodule Greeter do
  def hello(names, language_code \\ "en")
  def hello(names, language_code) when is_list(names) do
    names = Enum.join(names, ", ")
    
    hello(names, language_code)
  end

  def hello(name, language_code) when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

iex> Greeter.hello ["Sean", "Steve"], "es"
"Hola, Sean, Steve"
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!