在上一篇博客《漂亮的with,魚與熊掌可以兼得》中,展現了with的優雅之處,然而在比較with
與|>
時,言猶未盡,講得不夠透徹。
在那篇博客中,我說:
畢竟with/1并不是try/catch,它并不能捕獲執行中拋出的錯誤,然后轉向else進行錯誤處理。只有當模式匹配出現錯誤時,才會轉向else。
要優雅地處理錯誤,并用優雅的with/1將邏輯串聯起來,就需要重構get_user,get_response,send_response等函數。當程序邏輯正確時,返回一個tuple對象{:ok, result};如果出現錯誤,則返回{:error, error}。
如果進行了這樣的重構,是否意味著|>
也可以將健壯性與優雅結合起來呢?因為在Elixir中,函數的定義使用了模式匹配,因此,在定義參與|>
操作的函數時,可以通過模式匹配來考慮各種情況,這其中可以包含對{:error, error}
情形的處理,使得數據流不至于在流經該函數時因為錯誤而崩潰掉。
Joseph Kain在博客Learning Elixir's with給出了一個例子,執行了ecto查詢:
defp results(conn, search_params) do
conn.assigns.current_user
|> Role.scope(can_view: Service)
|> within(search_params)
|> all
|> preload(:user)
end
defp within(query, %{"distance" => ""}), do: {:ok, query}
defp within(query, %{"distance" => x, "location" => l} do
{dist, _} = Float.parse(x)
Service.within(query, dist, :miles, l)
end
defp within(query, _), do: {:ok, query}
defp all({:error, _} = result), do: result
defp all({:ok, query}), do: {:ok, Repo.all(query)}
defp preload({:error, _} = result), do: result
defp preload({:ok, enum}, field) do
{:ok, Repo.preload(enum, field)}
end
且不管業務,但我們可以清晰地看到在all
與preload
函數增加了對{:error, _}
分支的處理,這樣就可以避免數據流動的管道不至于因為錯誤而終止。
如果使用with
,雖然結構不如|>
清晰直觀,卻可以避免在all
與preload
中去處理錯誤分支。因為with
語句同樣使用了模式匹配,只要參與的方法不能滿足模式匹配的條件,就不會再執行do
,從而規避了錯誤引起的終止:
defp results(conn, search_params) do
with user <- conn.assigns.current_user,
query <- Role.scope(user, can_view: Service),
{:ok, query} <- within(query, search_params),
query <- all(query),
do: {:ok, preload(query, :user)}
end
defp within(query, %{"distance" => ""}), do: {:ok, query}
defp within(query, %{"distance" => x, "location" => l} do
{dist, _} = Float.parse(x)
Service.within(query, dist, :miles, l)
end
defp within(query, _), do: {:ok, query}
defp all(query), do: Repo.all(query)
defp preload(enum, field) do: {:ok, Repo.preload(enum, field)}
由于all/1
與preload/2
僅僅是對Repo.all/1
與Repo.preload/2
的簡單封裝,所以可以進一步簡化代碼:
defp results(conn, search_params) do
with user <- conn.assigns.current_user,
query <- Role.scope(user, can_view: Service),
{:ok, query} <- within(query, search_params),
query <- Repo.all(query),
do: {:ok, Repo.preload(query, :user)}
end
多余的代碼被有效地清除了,而功能與健壯性并沒有得到任何降低。這是within的奇妙之處。