在本篇文章中, 我們將運用 elixir 的 Pattern matching 來實現: function parse。

def parse(request) do
    # TODO: Parse the request string into a map:

end

首先, 我們回顧下 request:

request = """
GET /wildthings HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""

我們將運用 String.split/2 、List 與 Pattern matching, 從 request 中獲得我們所需的:

  • 方法: GET
  • 路徑: /wildthings

我們先看看 String.split/2 與 List 的用法:

  • 將 request 依 “\n” 進行劃分, 並將劃分後的字串, 放入 List lines 中:
iex(2)> lines = String.split(request, "\n")
["GET /wildthings HTTP/1.1", "Host: example.com",
 "User-Agent: ExampleBrowser/1.0", "Accept: */*", "", ""]
  • 我們將能很容易的獲取到 List lines 中的各個 String。例如: 取得 List lines 中的第一個 String: “GET /wildthings HTTP/1.1″。
iex(3)> first_line = List.first(lines)
"GET /wildthings HTTP/1.1"
  • 將上述的: “request 依 “\n” 進行劃分” 與 “取得 List lines 中的第一個 String”, 用 pipeline 給搞定 (簡潔的代碼是最重要的)
iex(4)> first_line = request |> String.split("\n") |> List.first
"GET /wildthings HTTP/1.1"
  • 再接再厲 ! 將 List first_line 依 ” ” 劃分, 並將劃分後的 String, 放入 List parts 中:
parts = String.split(first_line, " ")
["GET", "/wildthings", "HTTP/1.1"]

String.split/2 與 List 的用法就先交流到這裡。

接下來, 我們來談談 Pattern matching。

Pattern matching; 以等號 (=)  來表示; 同時代表著兩個操作: bind 斷言 match。

  • 將等號的右邊 bind 到等號的左邊。
  • 斷言等號的左邊是否與等號的右邊 match?

舉個最簡單的例子:

  • 將等號右邊的 1 bind 到等號左邊的 a; a 的值就是 1:
iex(6)> a = 1
1
iex(7)> a
1
  • 斷言等號左邊的 1 是否與等號右邊的 a 是 match 的 ? 當然是 match 的。
iex(8)> 1 = a
1
  • 斷言等號左邊的 2 是否與等號右邊的 a 是 match 的 ? 當然是不 match 的。
iex(9)> 2 = a
** (MatchError) no match of right hand side value: 1

我們再來看看 List 的 Pattern matching:

  • 等號右邊的 List [1, 2, 3] bind 等號左邊的 List [first, 2, last]; 使得 first = 1, last = 3。
  • 等號左邊的 List [first, 2, last] matches 等號右邊的 List [1, 2, 3]。
iex(11)> [first, 2, last] = [1,2,3]
[1, 2, 3]
iex(12)> first
1
iex(13)> last
3
iex(14)> 
  • 等號左邊的 List [first, 7, last] 不 matches 等號右邊的 List [1, 2, 3]。
iex(14)> [first, 7, last] = [1,2,3]
** (MatchError) no match of right hand side value: [1, 2, 3]
  • 等號左邊的 List [first, last] 不 matches 等號右邊的 List [1, 2, 3]。
iex(14)> [first,last] = [1,2,3]    
** (MatchError) no match of right hand side value: [1, 2, 3]

小總結下 Pattern matching:

  • Pattern matching 可以使我們獲取值; 將等號的右邊 bind 到等號的左邊。
  • Pattern matching 可以使我們斷言等號兩邊的 “值” 或 “結構” 是否一致 (Match) ?

交流完了 Pattern matching, 我們再回到 String first_line:

iex(14)> first_line
"GET /wildthings HTTP/1.1"

我們也再回顧下, String.split/2, 將回傳 List:

iex(15)> String.split(first_line, " ")
["GET", "/wildthings", "HTTP/1.1"]

所以, 我們將能很容易的運用 Pattern matching 就能獲得:

  • 方法: GET
  • 路徑: /wildthings
iex(16)> [method, path, version] = String.split(first_line, " ")
["GET", "/wildthings", "HTTP/1.1"]
iex(17)> method
"GET"
iex(18)> path
"/wildthings"
iex(19)> version
"HTTP/1.1"

當然, 我們不需要 version, 所以, 我們可以寫成:

iex(20)> [method, path, _] = String.split(first_line, " ")      
["GET", "/wildthings", "HTTP/1.1"]
iex(21)> method
"GET"
iex(22)> path
"/wildthings"

終於交流完了, String.split/2 、 List、Pattern matching。

我們可以將我們交流的內容, 寫回 function parse。

def parse(request) do
    # TODO: Parse the request string into a map:
    first_line = request |> String.split("\n") |> List.first
    [method, path, _] = String.split(first_line, " ")
end

將 method, path 寫入 key:value 的 map conv 中:

def parse(request) do
    # TODO: Parse the request string into a map:
    first_line = request |> String.split("\n") |> List.first
    [method, path, _] = String.split(first_line, " ")
    conv = %{ method: method, path: path, resp_body: ""}
 end

最後, 我們要將代碼變得簡潔; 我們只要簡潔的代碼

  • 去掉 conv; elixir 會自動將 function parse 最後一行的 Expression, 其所執行的結果, 當成是回傳值。
def parse(request) do
    # TODO: Parse the request string into a map:
    first_line = request |> String.split("\n") |> List.first
    [method, path, _] = String.split(first_line, " ")
    %{ method: method, path: path, resp_body: ""}
end
  • 再運用 pipeline, pattern matching; 傳說中的簡潔代碼就出現了。
def parse(request) do
    # TODO: Parse the request string into a map:
    [method, path, _] =
    request
    |> String.split("\n")
    |> List.first
    |> String.split(" ") 

    %{ method: method, path: path, resp_body: ""}
 end

樣例代碼如下:

defmodule Servy.Handler do
  def handle(request) do
    request
    |> parse
    |> route
    |> format_response

  end

  def parse(request) do
    # TODO: Parse the request string into a map:
    [method, path, _] =
    request
    |> String.split("\n")
    |> List.first
    |> String.split(" ")
    
    %{ method: method, path: path, resp_body: ""}
  end

  def route(conv) do
    # TODO: Create a new map that also has the response body:

  end

  def format_response(conv) do
    # TODO: Use values in the map to create an HTTP response string:

  end

end


request = """
GET /wildthings HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""

response = Servy.Handler.handle(request)
IO.puts response

elixir 的 pipeline 是有結構的; 上、下層之分或者說是有主 pipeline, 副 pipeline。

  • function handle 的 pipeline 就可以說是個主 pipeline; 這個主 pipeline 會 “流” 到 function parse。
  • function parse 內又有另外的 pipeline, 因而構成了一個副 pipeline。

elixir 中將會大量的使用 pattern matching; 毫無疑問的 pattern matching 將使得我們可正式的告別 if then else, 而使得代碼更加的簡潔。

在後續的文章中, 將有更多關於 pattern matching 的應用。

發表評論

您的電子郵箱地址不會被公開。 必填項已用*標註

此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據