在上一篇博客《漂亮的with珍逸,魚(yú)與熊掌可以兼得》中逐虚,展現(xiàn)了with的優(yōu)雅之處,然而在比較with
與|>
時(shí)谆膳,言猶未盡叭爱,講得不夠透徹。
在那篇博客中漱病,我說(shuō):
畢竟with/1并不是try/catch买雾,它并不能捕獲執(zhí)行中拋出的錯(cuò)誤,然后轉(zhuǎn)向else進(jìn)行錯(cuò)誤處理缨称。只有當(dāng)模式匹配出現(xiàn)錯(cuò)誤時(shí)凝果,才會(huì)轉(zhuǎn)向else。
要優(yōu)雅地處理錯(cuò)誤睦尽,并用優(yōu)雅的with/1將邏輯串聯(lián)起來(lái)器净,就需要重構(gòu)get_user,get_response当凡,send_response等函數(shù)山害。當(dāng)程序邏輯正確時(shí),返回一個(gè)tuple對(duì)象{:ok, result}沿量;如果出現(xiàn)錯(cuò)誤浪慌,則返回{:error, error}。
如果進(jìn)行了這樣的重構(gòu)朴则,是否意味著|>
也可以將健壯性與優(yōu)雅結(jié)合起來(lái)呢权纤?因?yàn)樵贓lixir中,函數(shù)的定義使用了模式匹配乌妒,因此汹想,在定義參與|>
操作的函數(shù)時(shí),可以通過(guò)模式匹配來(lái)考慮各種情況撤蚊,這其中可以包含對(duì){:error, error}
情形的處理古掏,使得數(shù)據(jù)流不至于在流經(jīng)該函數(shù)時(shí)因?yàn)殄e(cuò)誤而崩潰掉。
Joseph Kain在博客Learning Elixir's with給出了一個(gè)例子侦啸,執(zhí)行了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
且不管業(yè)務(wù)槽唾,但我們可以清晰地看到在all
與preload
函數(shù)增加了對(duì){:error, _}
分支的處理丧枪,這樣就可以避免數(shù)據(jù)流動(dòng)的管道不至于因?yàn)殄e(cuò)誤而終止。
如果使用with
庞萍,雖然結(jié)構(gòu)不如|>
清晰直觀拧烦,卻可以避免在all
與preload
中去處理錯(cuò)誤分支。因?yàn)?code>with語(yǔ)句同樣使用了模式匹配挂绰,只要參與的方法不能滿足模式匹配的條件屎篱,就不會(huì)再執(zhí)行do
,從而規(guī)避了錯(cuò)誤引起的終止:
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
僅僅是對(duì)Repo.all/1
與Repo.preload/2
的簡(jiǎn)單封裝葵蒂,所以可以進(jìn)一步簡(jiǎn)化代碼:
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
多余的代碼被有效地清除了,而功能與健壯性并沒(méi)有得到任何降低重虑。這是within的奇妙之處践付。