這是 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 | defmodule Geometry do |
def
表示公開函式,當然就另外還會有defp
表示私有函式囉
在沒有參數的函式上,使用如下
1 | defmodule Program do |
如果函式本身很短,那我可以寫成這樣子
1 | defmodule Geometry do |
回傳值
在 Elixir 的世界裡面,所有的東西都回傳值,當然函式也不例外
在函式中,最後一行程式碼就是這個函式的回傳值喔
1 | defmodule Geometry do |
參數(arity)
從前抓一個例子回來看
1 | defmodule Geometry do |
這個函式 rectangle_area
它接收兩個參數 a
和 b
,在 Elixir 的世界中我們稱呼為 Geometry.rectangle_area/2,我們稱呼數字為參數數目,這個參數數目很重要也不可省略,因為同樣名稱可以有不同的參數數目
如下例子,我們同時間有 Rectangle.area/1 和 Rectangle.area/2,這兩個函式是截然不同的!
1 | defmodule Rectangle do |
在 iex
中是這樣的
1 | iex(1)> Rectangle.area(5) |
我們也可以為函式設定預設參數,用法是在參數後面加 \\
符號
1 | defmodule MyModule do |
這個函式本身包含了 MyModule.fun/2,MyModule.fun/3 和 MyModule.fun/4 喔
匿名函式(Anonymous Functions)
在前面所講的都是所謂的 Named Functions,現在來介紹一下匿名函式或是稱為 lambda,使用方法會需要 fn
和 end
兩個關鍵字,然後使用 ->
來定義函式本體
1 | iex(1)> sum = fn a, b -> a + b end |
或者我們也可簡化用法,使用 &
符號
1 | iex(1)> sum = &(&1 + &2) |
這裡我們講兩個小細節
第一小細節,我們可以看到為什麼都要加一個 dot(.),通常來講加了 dot(.) 是表示這是一個匿名函式 sum.(2, 3)
,沒有加的表示命名函式 sum(2, 3)
,這是為了增加程式碼的可讀性
第二個小細節,匿名函式的參數建議不要用括號
1 | iex(1)> square = fn x -> |
命名函式的參數建議用括號
1 | defmodule Geometry do |
為什麼呢?賣個關子,我們在之後會說明喔!
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 | defmodule Geometry do |
當然我們可以有多個函式在同一個模組
1 | defmodule Geometry do |
一個檔案中可以有多個模組
1 | defmodule Module1 do |
或是我們之前提到的 dot(.)
1 | defmodule Geometry.Rectangle do |
還有階層式的模組關係
1 | defmodule Geometry do |
注意喔!上面這個關係根據 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 | defmodule MyModule do |
來看一下別名的範例
1 | defmodule MyModule do |
在上面的例子中我們使用別名
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 | defmodule Circle do |
這裡有一點要提醒的,不管是
defmodule
或是def
它們兩個都不是 Elixir 的關鍵字(keywords),他們都只是macros
而已唷
如何使用
上面寫的落落長,但是,但是
寫好一個模組或函式之後呢,那我們要怎麼樣使用它?
我們可以建立一個副檔名為 ex 的檔案(對!Elixir的 source file 都是這個副檔名唷)並且把上面的那些程式碼丟到裡面去,這一個一個的 ex 檔案都會像是副程式一樣的存在
那在 shell 底下怎麼測試呢,參考如下
1 | $ iex geometry.ex |