更新於 2024/10/28閱讀時間約 11 分鐘

《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)>

參考文獻

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.