function clauses elixir
在 “云端分布式架构下的编程语言: ELIXIR; Map” 一文中, 我们在 function route 中, 加入了 “Bears, Lions, Tigers” 到 map conv 中的 resp_body。
def route(conv) do
# TODO: Create a new map that also has the response body:
%{ conv | resp_body: "Bears, Lions, Tigers"}
end
但是, function route 目前只是具备了转换信息的功能; 将 resp_body 的空字串, 转换为: “Bears, Lions, Tigers”。
我们在本文中, 将运用 function clauses , 让 function route 能根据不同的 path, 而能转换 resp_body 为不同的值。
我们新增了另一个 request; path 是: /bears。
所以, 我们现在有两个 requests; 一个 request 的 path 是: /wildthings, 另一个request 的 path 是: /bears。
request = """
GET /wildthings HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*
"""
request = """
GET /bears HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*
"""
按照传统的编程方式, 我们就在 function route 中, 加入 if else。
def route(conv) do
# TODO: Create a new map that also has the response body:
if conv.path == "/wildthings" do
%{ conv | resp_body: "Bears, Lions, Tigers"}
else
%{ conv | resp_body: "Teddy, Smokey, Paddington"}
end
end
执行的结果是符合预期的:
$ elixir lib/servy/handler.ex
%{method: "GET", path: "/wildthings", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Bears, Lions, Tigers
%{method: "GET", path: "/bears", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 25
Teddy, Smokey, Paddington
我个人是很不喜欢 if then else 的。因为, if then else 很容易的就会形成所谓的 Deeply Nested Logic 的技术债务; 相关的研究报告指出, Deeply Nested Logic 的技术债务, 是形成软件产品约 20% 缺陷的主要根因。
elixir 的函数式编程的方式, 运用 pattern matching 与 function clauses , 使得代码能避免掉 Deeply Nested Logic 技术债务的发生。
function clauses 指的是: 名字相同, arity (参数的数目) 也相同的 function。
function clauses :
- 名字: route
- arity (参数的数目) : 3
- map conv
- http 方法: “GET”
- path: “/wildthings”, “/bears”
- function clauses 中的:
- 一个 function 负责 path 是: /wildthings 的信息转换。
- 另一个 function 负责 path 是: /bears 的信息转换。
def route(conv, "GET", "/wildthings") do
%{ conv | resp_body: "Bears, Lions, Tigers"}
end
def route(conv, "GET", "/bears") do
%{ conv | resp_body: "Teddy, Smokey, Paddington"}
end
我们将使用 pattern matching 的方法, 在 function clauses 中, 找到相匹配 (match) 的 function, 以进行相对应 path 的信息转换。
因为, 在 function clauses 中, function 的 arity 为 3, 所以, function route(conv); arity 为 1; 需藉由另一个 function route(conv, conv.method, conv.path); arity 为 3; 在 function clauses 中, 找到相匹配 (match) 的 function。
def route(conv) do
route(conv, conv.method, conv.path)
end
样例代码如下:
defmodule Servy.Handler do
def handle(request) do
request
|> parse
|> log
|> route
|> format_response
end
def log(conv), do: IO.inspect conv
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
route(conv, conv.method, conv.path)
end
def route(conv, "GET", "/wildthings") do
%{ conv | resp_body: "Bears, Lions, Tigers"}
end
def route(conv, "GET", "/bears") do
%{ conv | resp_body: "Teddy, Smokey, Paddington"}
end
def format_response(conv) do
# TODO: Use values in the map to create an HTTP response string:
"""
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: #{String.length(conv.resp_body)}
#{conv.resp_body}
"""
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
request = """
GET /bears HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*
"""
response = Servy.Handler.handle(request)
IO.puts response
执行结果如下:
$ elixir lib/servy/handler.ex
%{method: "GET", path: "/wildthings", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Bears, Lions, Tigers
%{method: "GET", path: "/bears", resp_body: ""}
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 25
Teddy, Smokey, Paddington