iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 15
1
Software Development

函數式編程: 從 Elixir & Phoenix 入門。系列 第 15

mix 專案,與使用其它模組裡的函式

本篇將解釋 mix project 的基本概念,以及各種函式的呼叫方式。

mix

mix 是 elixir 內建的多功能指令。它可以用來新建專案、安裝管理函式庫及可執行套件、執行測試、生成文件等等。絕大多數 elixir 的套件都會使用 mix 的各種功能,來讓大家的操作方式更加一致。

我們先試試用 mix 建立一個專案叫 hello_elixir,打開 shell,輸入以下指令

$ mix new hello_elixir
$ cd hello_elixir

https://ithelp.ithome.com.tw/upload/images/20180103/20103390I3Db9QiS3k.png

可以看到 mix 新增了一個叫 hello_elixir 的資料夾,並在裡面生成了許多檔案。我們剛剛已經用 cd 切換到這個目錄裡了。接下來我們用編輯器打開 mix.exs,它的內容長這樣:

defmodule HelloElixir.MixProject do
  use Mix.Project

  def project do
    [
      app: :hello_elixir,
      version: "0.1.0",
      elixir: "~> 1.6-rc",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  def application do
    #...
  end
  defp deps do
    #...
  end
end

其中 project 函式裡定義了這個專案的基本資料,像是名稱,版本及需要的 elixir 版本等等。而底下的 applicationdeps 分別是這個執行期需要載入的模組,及這個專案依賴的外部函式庫。不過這部份不是今天的重點,在後面的章節會提到什麼時候需要改動這個檔案。

依照 mix 專案的慣例,主要的程式碼都會放在 lib 資料夾底下。打開 lib/hello_elixir,我們可以看到已經生成了一些樣版的程式碼。最上方的模組名稱,是 HelloElixir,就是把專案名稱 CamelCase 化。

我們先在這個檔案裡 hello 函式的底下加入一個新的函式,讓它看起來像是這樣:

def hello do
  :world
end

def foo do
  "Foo the #{hello()}"
end

def bar(list) do
  Enum.max(list) + 1
end

foo 函式裡我們示範了同一個模組下,我們可以直接呼叫其它的函式,不需要加上模組名稱。而若是想呼叫其它的模組的函式時,就像在 bar 函式中的示範,就必須加上模組名稱來呼叫: Enum.max(list)

執行專案裡的程式

由於 elixir 的內建工具鏈設計的相當完整,所以他們之間可以很緊密的互動。我們先回到 shell ,在 hello_elixir 目錄下打開之前用過的 iex 互動介面,但是這次的指令有一點點不一樣:

$ iex -S mix

iex-S 參數可以讓互動式介面找到路徑裡的 script 並執行,這樣一來我們就把互動式介面就類似掛載進當前目錄下的 mix 專案裡了。來試著呼叫剛定義好的函式吧:

iex(1)> HelloWorld.foo
"Foo the world"

Note: 當你輸入 Hel 的時後,按下鍵盤上的 [TAB],會有自動補完的功能。如果可以補完的選項不只一個,按兩下會出現所有可用的補完項目。

資料夾結構慣例

接著我們來多做一個模組。在 lib 底下新增一個資料夾叫 helpers,並在其下新增一個檔案,叫baz_helper.ex。在檔案裡我們寫入這些內容:

defmodule HelloElixir.Helpers.BazHelper do
   def min(list) do
     Enum.min(list)
   end

   def zoom do
     "Zoom!!"
   end

   defp hoo do
     "This is a private function"
   end
end

Elixir 的模組命名慣例,除了會用大寫 CamelCase 之外,會是 專案名.路徑.檔案名,像是這裡示範的 HelloElixir.Helpers.BazHelper

alias 縮寫

如果想在其它模組中呼叫 BazHelper 的函式,除了每次都使用全名之外,可以用 alias 模組全名 來減少需要打的字,後續的呼叫只要輸入模組名稱的最後一部份即可。

defmodule HelloElixir do
  alias HelloElixir.Helpers.BazHelper

  def try_alias do
    BazHelper.baz
  end
end

alias... as: 來改用其它名字

如果有多個重覆的名稱,或是你覺得原先的名稱不好打,可以用 as: 選項改取其它名稱。但是一般來說非必要不會使用這個技巧,因為其它讀程式的人,得要翻回檔案最上方才知道這個模組原先的名字是什麼。

defmodule HelloElixir do
  alias HelloElixir.Helpers.BazHelper, as: Baz

  do try_as do
   Baz.baz
  end
end

一次 alias 多個模組

alias HelloElixir.Helper.{BazHelper, BooHelper, ZooHelper}

連模組名稱都不想用: import

如果你發現這兩個模組緊密相關,常常會用到該模組的函式,不想每次都多打字時,可以使用 import,只要函式名稱沒有衝突,就可以當做同一個模組的函式來呼叫。

defmodule HelloElixir do
  import HelloElixir.Helpers.BazHelper

  def try_import do
    hoo()
  end
end

所以其實你在寫 elixir 時,可以自由的使用四則運算、 elem/2def/2 等函式,就是因為每個 elixir 模組,都預設 importKernel 模組底下的所有函式。

只想要部份的函式

如果你只想要部份的函式,你可以使用 only 選項。

import HelloElixir.Helpers.BazHelper, only: [baz: 1]

import 接受一個 keyword list,鍵是想要匯入的函式名稱,值是參數個數,可以一次匯入多個函式。

另外也可以改用 except 選項,去除不想要的函式。如果你想要覆寫 Kernel 裡已經有的函式,你就可以這樣做:

defmodule HelloElixir do
  import Kernel, except: [elem: 2]

  def elem(list, _index) do
    Enum.at(list, Enum.random(1..length(list)))
  end
end

使用 macros:require

Elixir 中提供了強大的元編程 (meta programming) 能力,所以一般函式做不到的事,可以撰寫 macro 來處理。在這裡我們只要先知道 macro 類似功能更強大的函式就可以了。而想要使用某個模式下的所有 defmacro 定義的話,要先用 require 模組名稱。例如 elixir 內建的 Interger 模組:

iex> require Integer
iex> Integer.is_odd(3)
true

比繼承更好的就是合成: use

除了繼承之外,有些程式語言還提供了不同的機制,來將已經寫好的函式合成到其它檔案中,例如 Ruby 就有 mixin 讓你把函式插入目前的類別裡。而在 elixir 裡,則提供了 use 模組名稱的語法。當你在某個模組裡,寫了 use Aaa 時,會呼叫 Aaa 模組下的 __using__ 這個 macro。裡面可以自由定義該模組會在 use 它的的模組裡做什麼事。

目前我們只要知道 use 可以對你的模組做出某些改變,讓它有一些特殊的功能 (通常就是合成一些函式進來)。簡單的例子可以看看我寫的 pipe_to,它提供了 use PipeTo.Override,讓你的 |> 運算子可以選擇要 pipe 到不同的參數位置。這種能力就是運用這個機制做出來的。更詳細的部份,就留待之後有篇幅再來講解了。


進行到一半,我們終於把 Elixir 的語法部份大致交待完了。只差 macro、protocol、behaviour (沒有拼錯字) 及 with 等比較進階的功能。下一篇,我們就要開始來探索 Phoenix 這個框架了。

Happy Hacking! 明天見。


上一篇
Sigil 及 Struct
下一篇
那種會從灰燼裡復活的鳥: Phoenix
系列文
函數式編程: 從 Elixir & Phoenix 入門。31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言