继续探索with语句的奇妙之处

开发 开发工具
在上一篇博客《漂亮的with,鱼与熊掌可以兼得》中,展现了with的优雅之处,然而在比较with与|>时,言犹未尽,讲得不够透彻。本文我们继续探索with语句的奇妙之处。

[[189165]]

在上一篇博客《漂亮的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查询:

  1. defp results(conn, search_params) do 
  2.     conn.assigns.current_user 
  3.     |> Role.scope(can_view: Service) 
  4.     |> within(search_params) 
  5.     |> all 
  6.     |> preload(:user) 
  7. end 
  8.  
  9. defp within(query, %{"distance" => ""}), do: {:ok, query} 
  10. defp within(query, %{"distance" => x, "location" => l} do 
  11.     {dist, _} = Float.parse(x) 
  12.     Service.within(query, dist, :miles, l) 
  13. end  
  14. defp within(query, _), do: {:ok, query} 
  15.  
  16. defp all({:error, _} = result), do: result 
  17. defp all({:ok, query}), do: {:ok, Repo.all(query)} 
  18.  
  19. defp preload({:error, _} = result), do: result 
  20. defp preload({:ok, enum}, field) do 
  21.     {:ok, Repo.preload(enum, field)} 
  22. end 

且不管业务,我们可以清晰地看到在all与preload函数增加了对{:error, _}分支的处理,这样就可以避免数据流动的管道不至于因为错误而终止。

如果使用with,虽然结构不如|>清晰直观,却可以避免在all与preload中去处理错误分支。因为with语句同样使用了模式匹配,只要参与的方法不能满足模式匹配的条件,就不会再执行do,从而规避了错误引起的终止:

  1. defp results(conn, search_params) do 
  2.     with user <- conn.assigns.current_user, 
  3.          query <- Role.scope(user, can_view: Service), 
  4.          {:ok, query} <- within(query, search_params), 
  5.          query <- all(query), 
  6.     do: {:ok, preload(query, :user)} 
  7. end 
  8.  
  9. defp within(query, %{"distance" => ""}), do: {:ok, query} 
  10. defp within(query, %{"distance" => x, "location" => l} do 
  11.     {dist, _} = Float.parse(x) 
  12.     Service.within(query, dist, :miles, l) 
  13. end defp within(query, _), do: {:ok, query} 
  14.  
  15. defp all(query), do: Repo.all(query) 
  16.  
  17. defp preload(enum, field) do: {:ok, Repo.preload(enum, field)} 

由于all/1与preload/2仅仅是对Repo.all/1与Repo.preload/2的简单封装,所以可以进一步简化代码:

  1. defp results(conn, search_params) do 
  2.     with user <- conn.assigns.current_user, 
  3.          query <- Role.scope(user, can_view: Service), 
  4.          {:ok, query} <- within(query, search_params), 
  5.          query <- Repo.all(query), 
  6.   do: {:ok, Repo.preload(query, :user)} 
  7. end 

多余的代码被有效地清除了,而功能与健壮性并没有得到任何降低。这是within的奇妙之处。

【本文为51CTO专栏作者“张逸”原创稿件,转载请联系原作者】

戳这里,看该作者更多好文

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2010-01-12 10:45:42

C++教程

2013-04-16 14:42:38

云计算智能手机移动通信网络

2024-02-26 18:23:29

C++封装代码

2017-01-19 17:54:31

曙光服务器

2024-04-03 09:43:06

C++编程代码

2011-08-02 13:04:40

SQL Server

2024-02-22 10:36:13

SELECT 语句PostgreSQL数据查询

2024-01-29 16:55:38

C++引用开发

2018-11-02 16:16:41

程序硬盘存储

2009-10-26 15:26:37

VB.NET属性

2024-03-15 15:03:23

2012-04-16 09:16:48

2011-05-24 13:33:45

2011-03-15 13:57:46

2010-01-14 18:02:05

C++语言

2010-03-16 13:20:18

Python pyli

2019-04-28 11:11:26

沙箱网络攻击网络安全

2015-07-17 07:47:51

京东618订

2021-01-05 14:39:30

人工智能机器人购物

2017-09-18 13:40:42

Docker CompGPUTensorFlow
点赞
收藏

51CTO技术栈公众号