|>
)在 Pipe operator 之前,我們先複習一下函式呼叫
假如我們得到一個使用者輸入的 params,
長這樣
%{"input" => %{"name" => " jack"}}
但我們想要從這個結構拿出 name 的值,
去掉不小心輸入的前後空白,
並且把第一個字大寫
params = %{"input" => %{"name" => " jack"}}
input = Map.get(params, "input")
raw_name = Map.get(input, "name")
trimed_name = String.trim(raw_name)
capitalized_name = String.capitalize(trimed_name)
capitalized_name
#=> "Jack"
但是這樣子寫實在是不好閱讀,也增加了很多個暫時的變數
String.capitalize(String.trim(Map.get(Map.get(params, "input"), "name")))
這樣子雖然省掉中間暫時的變數,但是更難讀了
Elixir 與一些 Functional 語言都有了這個情形提供了 Pipe operator |>
使用 |>
後:
params
|> Map.get("input")
|> Map.get("name")
|> String.trim()
|> String.capitalize()
|>
的作用是,把前面值當成後面的第一個參數
例如
params |> Map.get("input")
就等於
Map.get(params, "input")
我們把全部的步驟使用 |>
串起來後,
程式碼會變得很白話很好閱讀,就像步驟一樣一行一行的寫下去。
每次寫這種一連串的處理我都會想到洋芋片工廠的生產線,或是 Subway 的點餐
?
的函式例如 Enum.any?/1
在 Elixir 裡,函式名稱最後可以是 ?
在慣例裡,如果一個函式以問號結尾,通常他的回傳值都是 true
或是 false
!
的函式函式名稱最後也可以是 !
通常這種方法都會有 加!
版本與不加的版本,
例如 File.read
與 File.read!
沒有加上!
的 File.read
,
在呼叫後會回傳在 Elixir 非常常見的成功或失敗 tuple 組合
File.read("README.md")
#=> {:ok, "內容"}
File.read("No_such_file.txt")
#=> {:error, :enoent}
通常我們會用 case 把他的結果接起來,然後依照回覆的狀況處理,例如
case File.read("README.md") do
{:ok, content} ->
# 處理我們拿到內容後要做的事,這邊用一個虛構的 display_content/1 函式示意
display_content(content)
{:error, reason} ->
# 處理錯誤,可能是記錄在系統或是回饋給使用者,這邊用一個虛構的 send_warning/1 函式示意
send_warning(reason)
end
如果確定這個呼叫通常會正確,在這邊寫正常不會使用的錯誤有點浪費時間
我們可以使用有 !
版本的函式,
如果呼叫成功他直接回傳內容,如果失敗則是直接 raise 錯誤,交由上一層的錯誤機制處理
(如:Phoenix 會在網頁回覆錯誤頁面或是重新整理)
(後面如果有篇幅,我們可以探索一下 Elixir 著名的錯誤容許機制與 process 管理練,但是現在先不用擔心)
File.read!("README.md")
|> display_content()
# 可以不用處理錯誤,直接交給下一步使用
建立一個內有 滷肉飯:100\n炸牛丼飯:160\n炸蝦丼飯:150\n炸雞:70
的 menu.txt 檔案
可以使用這個 script 在終端機產生
echo "滷肉飯:100\n炸牛丼飯:160\n炸蝦丼飯:150\n炸雞:70" >> menu.txt
寫一個函式,讀取 menu.txt 檔案中的文字
並將其轉成我們比較好用的 map
Menu.get("menu.txt")
#=>
%{
"滷肉飯" => 100,
"炸牛丼飯" => 160,
"炸蝦丼飯" => 150,
"炸雞" => 70
}
提示:先把要做的步驟寫下來,再去找相對應的函式
這邊比較難找的應該是要把目前處理到一半的資料轉成 Map
可以使用 Enum.into/3,
但其實解法很多,這邊先熟悉作法為主,可以先不擔心效率
在實作的時候可以在 iex 試試看
(小祕技:在 iex 裡 v()
會是上一行的結果, 可以執行 h v
來看 v
函式有什麼功能)
defmodule Menu do
def get(path) do
path
|> File.read!()
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, ":"))
|> Enum.into(%{}, fn [k, v] -> {k, String.to_integer(v)} end)
end
end