
在本篇文章中, 我们将运用 elixir 的 Pattern matching 来实现: function parse。
elixir function parse pattern matching
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 的应用。