上篇博文《漂亮的with,鱼与熊掌可以兼得》展示了with的优雅。但是和|>比较,还是不够,解释的不够透彻。在那篇博客里,我说:毕竟with/1不是try/catch,它不能捕获执行过程中抛出的错误,然后转向else进行错误处理。只有模式匹配出错才会转向else。为了优雅地处理错误,将逻辑与优雅的with/1连接起来,需要重构get_user、get_response、send_response等函数。当程序逻辑正确时,返回一个元组对象{:ok,result};如果发生错误,返回{:error,error}。如果进行了这样的重构,是不是意味着|>也可以兼顾健壮和优雅呢?因为在Elixir中,函数的定义使用了模式匹配,所以在定义参与|>操作的函数时,可以通过模式匹配来考虑各种情况,其中可以包括对{:error,error}情况的处理,这样数据流在通过函数时不会因为错误而崩溃。JosephKain在博客LearningElixir'swith中给出了一个示例,执行ecto查询:defpresults(conn,search_params)doconn.assigns.current_user|>Role.scope(can_view:Service)|>within(search_params)|>all|>preload(:user)enddefpwithin(query,%{"distance"=>""}),do:{:ok,query}defpwithin(query,%{"distance"=>x,"location"=>l}do{dist,_}=Float.parse(x)Service.within(query,dist,:miles,l)enddefpwithin(query,_),do:{:ok,query}defpall({:error,_}=result),do:resultdefpall({:ok,query}),do:{:ok,Repo.all(query)}defppreload({:error,_}=result),do:resultdefppreload({:ok,enum},字段)do{:ok,Repo.preload(enum,field)}end不管业务如何,我们可以清楚的看到在all和preload函数中加入了{:error,_}分支的处理,这样数据就可以beavoided流水线不会因为错误而终止,如果使用with,虽然结构不如|>清晰直观,但可以避免在all和preload中处理错误分支。因为with语句也使用了模式匹配,只要参与的方法不能满足模式匹配的条件,do就不会再执行,从而避免了错误导致的终止:defpresults(conn,search_params)dowithuser<-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)}enddefpwithin(query,%{"distance"=>""}),do:{:ok,query}defpwithin(query,%{"distance"=>x,"location"=>l}do{dist,_}=Float.parse(x)Service.within(query,dist,:miles,l)enddefpwithin(query,_),do:{:ok,query}defpall(query),do:Repo.all(query)defppreload(enum,field)do:{:ok,Repo.preload(enum,field)}由于all/1和preload/2只是Repo.all/1和Repo.preload/2的简单封装,所以可以进一步简化代码:defpresults(conn,search_params)dowithuser<-conn.assigns.current_user,query<-Role.scope(user,can_view:Service),{:ok,query}<-within(query,search_params),查询<-Repo.all(query),do:{:ok,Repo.preload(query,:user)}end有效清除了冗余代码,功能和健壮性没有降低。这就是内在的奇妙。【本文为专栏作家“张艺”原创稿件,转载请联系原作者】点此阅读更多该作者好文
