sujingjhong.com


Elixir 101 / 函式與回傳值

最近又重新開始學 Elixir,這時遇到一個問題:

在 Elixir 裡面要怎麼建立函式,還有函數要怎麼回傳值?

首先這要從 Elixir 有兩種函數類型說起:

  • 匿名函式(Anonymous Functions)
  • 具名函示(Named Functions)

匿名函式 (Anonymous Functions) #

匿名函式 (Anonymous Functions),通常指的是這個函式無需有函式的識別符號(identifier)。在有些語言實作上,被稱為 lambda表示式(Pyhton),或者是 閉包(closures) (Rust)。

在 Elixir 裡,匿名函式是宣告一個變數,然後將函式用 fn ... end 包裹起來。

例如我們宣告一個叫 hello_world 的匿名函式,裡面是向顯示器顯示 Hello World ,那我們可以寫作:

hello_world = fn() -> IO.puts "Hello World" end

這時候我們該如何執行這個函式呢?

在其他語言中,可能會很自然的加上 () 作為執行函式的句法,例如上方的 hello_world 變成 hello_world() 來執行。接著就會在 Elixir 中發生錯誤。例如我們在 iex 中試試看:

$ iex
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> hello_world = fn() -> IO.puts "Hello World" end
#Function<21.126501267/0 in :erl_eval.expr/5>
iex(2)> hello_world()
** (CompileError) iex:2: undefined function hello_world/0

在 Elixir 中,必須加入 . (dot) 來執行這個匿名函式(. (dot) 在 Elixir 裡面也是一個運算符號,基於本篇主題,留待下次再說明)。所以執行上方範例的 hello_world 就要變成:

hello.()

這時候我們在 iex 在實驗一次,就可以知道成功執行啦!

$ iex
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> hello_world = fn() -> IO.puts "Hello World" end
#Function<21.126501267/0 in :erl_eval.expr/5>
iex(2)> hello_world.()
Hello World
:ok
iex(3)>

具名函式(Named Functions) #

相對於匿名函式,具名函式具有識別符。不過在 Elixir 裡面宣告一個具名函式,和其他語言一些不同。例如在 JavaScript 我們可以直接用 function 語法宣告一個 helloWorld 的具名函式:

function helloWorld(){
  console.log('hello world');
}

但在 Elixir 裡面我們可以這樣做嗎?是不是可以將匿名函式的 fn...end 語法直接拿來宣告為:

fn hello_world = () -> IO.puts "hello world"

答案是不能的。

在 Elixir 裡,具名函式的運作範圍僅限於模組(module)。換言之,必須將函式定義在 module 裡。例如我們定義一個 MyModule 名稱的 module,裡面有一個具名函式稱為 hello_world

defmodule MyModule do
	def hello_world() do
		IO.puts "hello world"
	end
end

當我們在 module 裡面定義好一個具名函式,這時候我們一樣就可以用 . (dot) ,來呼叫這個 module 裡面特定的具名函式。例如上述案例,我們要呼叫 MyModule 裡面的 hello_world 我們就是:

MyModule.hello_world()

在 iex 裡面實驗一次:

$ iex
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule MyModule do
...(1)> def hello_world do
...(1)> IO.puts "hello world"
...(1)> end
...(1)> end
{:module, MyModule,
 <<70, 79, 82, 49, 0, 0, 4, 104, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 149,
   0, 0, 0, 15, 15, 69, 108, 105, 120, 105, 114, 46, 77, 121, 77, 111, 100, 117,
   108, 101, 8, 95, 95, 105, 110, 102, 111, ...>>, {:hello_world, 0}}
iex(2)> MyModule.hello_world()
hello world
:ok
iex(3)>

函式的回傳值 #

以上介紹了兩種函示:匿名函式以及具名函式。那我們在函式中,要怎麼回傳函式的運算值呢?

在程式語言中,函式回傳值主要有兩種表示方式:

  • return 或相類似的語法
  • 回傳值為函式結構中最後的表示式

第一種方式就例如 JavaScript,在函式結構中用 return 語法回傳函式運算值,如果未設定的話,就是回傳 undefined

function helloWorld(shouldReturn){
  if(shouldReturn){
		return 'hello world'; 
  }
}

console.log(helloWorld(true));  // 將輸出 'hello world'
console.log(helloWorld(false)); // 將輸出 undefined

而在 Elixir 裡,則是採用第二種方式,將函式結構中最後表示式作為回傳值。如果沒有,則回傳 nil ,即空值。

以之前 MyModule 為例,我們新增另一個 hello 具名函式,而他的函式結構中未有任何程式碼。並將 hello_world 中的 IO.puts "hello world" 改為 "hello world"

defmodule MyModule do
	def hello_world() do
		"hello world"
	end
	
	def hello() do
	end
end

這時候 hello_world 將回傳字串值 hello_world ,而 hello 將僅回傳 nil。我們可以在 iex 中試一下:

$ iex
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule MyModule do
...(1)> def hello_world() do
...(1)> 'hello world'
...(1)> end
...(1)> def hello() do
...(1)> end
...(1)> end
{:module, MyModule,
 <<70, 79, 82, 49, 0, 0, 4, 120, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 144,
   0, 0, 0, 15, 15, 69, 108, 105, 120, 105, 114, 46, 77, 121, 77, 111, 100, 117,
   108, 101, 8, 95, 95, 105, 110, 102, 111, ...>>, {:hello, 0}}
iex(2)> MyModule.hello_world()
'hello world'
iex(3)> MyModule.hello()
nil
iex(4)>

參考文獻 #