你的程式碼聞起來很臭嗎?不用擔心!大夫幫你...越治越臭

Function & Module(Elixir從零開始系列 02)(鼠年全馬鐵人挑戰 W03)

這是 w3HexSchool 鼠年全馬鐵人挑戰 Week 3 唷

這是 Elixir 從零開始 系列 02 唷

前言

在前一篇裡面我們有提到 interactive shell,在電腦操作上我們可以透過在終端機上輸入 iex 來進入並進行一行一行的操作,但是我們也不可能寫程式的時候都打一行等結果再打下一行吧,終究我們需要用到一些方法來把一整段的程式碼包在一起批次執行,而這些方法就會是今天要介紹的 Function & Module

Function

定義

在函式(Function)命名上面,通常我們開頭以小寫字母或底線開頭,中間任意混搭並配合數個底線區隔字句意義,最後結尾可以為?或!通常來講結尾為!代表這函式可能有 runtime error,結尾為?代表這函式回傳 true or false,當然,以上講的都是通則,並不是一定

以下為一些命名範例

  • rectangle_area
  • cal_the_result
  • output_2_screen
  • test!
  • is_balance?

在使用函式上面,我們開頭使用 def,並且利用 do...end 來包含邏輯部分

1
2
3
4
5
defmodule Geometry do
def rectangle_area(a, b) do
a * b
end
end

def 表示公開函式,當然就另外還會有 defp 表示私有函式囉

在沒有參數的函式上,使用如下

1
2
3
4
5
defmodule Program do
def run do
...
end
end

如果函式本身很短,那我可以寫成這樣子

1
2
3
defmodule Geometry do
def rectangle_area(a, b), do: a * b
end

回傳值

在 Elixir 的世界裡面,所有的東西都回傳值,當然函式也不例外

在函式中,最後一行程式碼就是這個函式的回傳值喔

1
2
3
4
5
defmodule Geometry do
def rectangle_area(a, b) do
a * b # Calculates the area and returns the result
end
end

參數(arity)

從前抓一個例子回來看

1
2
3
4
5
defmodule Geometry do
def rectangle_area(a, b) do
a * b
end
end

這個函式 rectangle_area 它接收兩個參數 ab,在 Elixir 的世界中我們稱呼為 Geometry.rectangle_area/2,我們稱呼數字為參數數目,這個參數數目很重要也不可省略,因為同樣名稱可以有不同的參數數目

如下例子,我們同時間有 Rectangle.area/1 和 Rectangle.area/2,這兩個函式是截然不同的!

1
2
3
4
5
defmodule Rectangle do
def area(a), do: area(a, a)

def area(a, b), do: a * b
end

iex 中是這樣的

1
2
3
4
5
iex(1)> Rectangle.area(5)
25

iex(2)> Rectangle.area(5,6)
30

我們也可以為函式設定預設參數,用法是在參數後面加 \\ 符號

1
2
3
4
5
defmodule MyModule do
def fun(a, b \\ 1, c, d \\ 2) do
a + b + c + d
end
end

這個函式本身包含了 MyModule.fun/2,MyModule.fun/3 和 MyModule.fun/4 喔

匿名函式(Anonymous Functions)

在前面所講的都是所謂的 Named Functions,現在來介紹一下匿名函式或是稱為 lambda,使用方法會需要 fnend 兩個關鍵字,然後使用 -> 來定義函式本體

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

或者我們也可簡化用法,使用 & 符號

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

這裡我們講兩個小細節

第一小細節,我們可以看到為什麼都要加一個 dot(.),通常來講加了 dot(.) 是表示這是一個匿名函式 sum.(2, 3),沒有加的表示命名函式 sum(2, 3),這是為了增加程式碼的可讀性

第二個小細節,匿名函式的參數建議不要用括號

1
2
3
iex(1)> square = fn x ->
x * x
end

命名函式的參數建議用括號

1
2
3
4
5
defmodule Geometry do
def rectangle_area(a, b) do
a * b
end
end

為什麼呢?賣個關子,我們在之後會說明喔!

Module

定義

我們定義模組(Module)就是一堆函式集合在一起,而函式只能在模組之中被定義

在命名上面,我們通常開頭大寫並且使用 CamelCase style,這 style 不懂可以去 Google 一下,而模組名稱可以有底線和 dot(.) 後者經常用來做結構區分,我們來舉一些命名例子

  • Geometry.Rectangle
  • Module6
  • DoSomething

在 Elixir 中有很多已經定義好的模組可以使用唷,比如說 IO 模組,它是用來針對 I/O 做操作,此模組裡面的一個函式 puts 可以在螢幕中印出訊息

1
2
3
iex(1)> IO.puts("Hello World!")
Hello World!
:ok

我們可以看到呼叫模組的語法如下

ModuleName.function_name(args)

:ok 則是 IO.puts 的回傳值

在使用模組上面,我們開頭使用 defmodule,並且利用 do...end 來包含各個函式

1
2
3
4
5
defmodule Geometry do
def rectangle_area(a, b) do
a * b
end
end

當然我們可以有多個函式在同一個模組

1
2
3
4
5
6
7
8
9
defmodule Geometry do
def rectangle_area(a, b) do
a * b
end

def square_area(a) do
rectangle_area(a, a)
end
end

一個檔案中可以有多個模組

1
2
3
4
5
6
7
defmodule Module1 do
...
end

defmodule Module2 do
...
end

或是我們之前提到的 dot(.)

1
2
3
4
5
6
7
defmodule Geometry.Rectangle do
...
end

defmodule Geometry.Circle do
...
end

還有階層式的模組關係

1
2
3
4
5
6
defmodule Geometry do
defmodule Rectangle do
...
end
...
end

注意喔!上面這個關係根據 Elixir in Action 的解釋如下,這部分我也看不太懂,先存起來 XD

The child module can be referenced with Geometry.Rectangle. Again, this nesting is

a convenience. After the compilation, there’s no special relation between the modules

Geometry and Geometry.Rectangle.

引用和別名(Imports and aliases)

話不多說,直接看引用的範例

1
2
3
4
5
6
7
defmodule MyModule do
import IO # here we import the module IO

def my_function do
puts "Calling imported function." # here we use puts instead of IO.puts
end
end

來看一下別名的範例

1
2
3
4
5
6
7
defmodule MyModule do
alias Geometry.Rectangle, as: Rectangle # Sets up an alias to a module

def my_function do
Rectangle.area(...) # Calls a module function using the alias
end
end

在上面的例子中我們使用別名

1
alias Geometry.Rectangle, as: Rectangle # Sets up an alias to a module

Elixir 提供了一個甜甜的語法糖,程式可以直接擷取最後一部分當作是別名,這樣可以偷懶一些字不用打,如下

1
alias Geometry.Rectangle # Sets up an alias to a module

上面的別名一樣是 Rectangle 喔

屬性(Module attributes)

Module attributes 這個有點類似 C# 中的類別Property,定義好之後就可以在模組內使用,我們用 @ 這個符號來新增和使用屬性,另外 Elixir 也預先定義了一些像是 @moduledoc@doc 等,這裡我不多介紹,程式參考如下

1
2
3
4
5
defmodule Circle do
@pi 3.14159
def area(r), do: r * r * @pi
def circumference(r), do: 2 * r * @pi
end

這裡有一點要提醒的,不管是 defmodule 或是 def 它們兩個都不是 Elixir 的關鍵字(keywords),他們都只是 macros 而已唷

如何使用

上面寫的落落長,但是,但是

寫好一個模組或函式之後呢,那我們要怎麼樣使用它?

我們可以建立一個副檔名為 ex 的檔案(對!Elixir的 source file 都是這個副檔名唷)並且把上面的那些程式碼丟到裡面去,這一個一個的 ex 檔案都會像是副程式一樣的存在

那在 shell 底下怎麼測試呢,參考如下

1
2
3
$ iex geometry.ex
iex(1)> Geometry.rectangle_area(6, 7)
42

參考資料

  1. https://elixirschool.com/en/lessons/basics/modules/
  2. https://elixir-lang.org/getting-started/module-attributes.html