论坛首页 编程语言技术论坛

Ruby函数式风格编程

浏览 21729 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-02-05  
虽然打算把自己的一个开源项目移植到erlang,但手头的一个工作项目却是在用ruby on rails,花了一段时间了解ruby后,发现ruby还是有点意思的。(反倒是不太喜欢Rails。我喜欢直来直去的编程,rails里面太多的magic,实在让我这个喜欢刨根究底的人难受)。

回到主题,Ruby之所以比Python、groovy和Javascript更适合函数风格的编程,尤其是erlang风格,主要因为以下几点:
  • everything is message(也即任何语句都会返回值)
  • may return multiple values(可以返回多值)
  • parallel assignment(并行赋值)
让我们看几个例子:

例一、
命令式风格:
ruby 代码
 
  1. cond = {}  
  2. if par[:id]  
  3.   feed = Feed.find(par[:id])  
  4.   if feed  
  5.     cond[:feed] = feed.id  
  6.   end  
  7. end  
  8. if par[:m]
  9.   limit = par[:m].to_i  
  10. else  
  11.   limit = 20  
  12. end  
  13. if limit >= 4096  
  14.   limit = 4096  
  15. end  
  16. cond[:limit] = limit  
  17. if par[:d]  
  18.   days = par[:d].to_f  
  19.   if days <= 0 || days >= 365  
  20.     days = 365  
  21.   end  
  22.   cond[:time] = Time.now - days*86400  
  23. end  
函数时风格:
ruby 代码
 
  1. cond = {  
  2.   :feed   => if par[:id]  
  3.                feed = Feed.find(par[:id])  
  4.                feed ? feed.id : nil  
  5.              end,  
  6.   :limit  => begin  
  7.                 limit = par[:m] ? par[:m].to_i : 20  
  8.                 limit >= 4096 ? 4096 : limit  
  9.              end,  
  10.   :time   => if par[:d]  
  11.                days = par[:d].to_f  
  12.                days = days <= 0 || days >= 365 ? 365 : days  
  13.                Time.now - days * 86400  
  14.              end,  
  15. }.delete_if { |k, v| v.nil? } # delete all nil elements of cond  

注意15行删nil项,因为在一个if .. end子句中,总会返回一个值,在不满足所有条件的情况下,就是返回nil

例二
命令式风格:
ruby 代码
 
  1. if f[:mode] == "rss"  
  2.   rss = f[:feed]  
  3.   params[:feed][:channel] = rss.channel.title  
  4.   params[:feed][:description] = rss.channel.description  
  5.   params[:feed][:link] = rss.channel.link  
  6.   params[:feed][:copyright] = rss.channel.copyright  
  7. else  
  8.   atom = f[:feed]  
  9.   params[:feed][:channel] = atom.title  
  10.   params[:feed][:description] = atom.subtitle  
  11.   params[:feed][:link] = atom.links.join  
  12.   params[:feed][:copyright] = atom.rights  
  13. end  
函数式风格:
ruby 代码
 
  1. params[:feed][:channel],  
  2. params[:feed][:description],  
  3. params[:feed][:link],  
  4. params[:feed][:copyright] = if f[:mode] == "rss"  
  5.                               rss = f[:feed]  
  6.                               a, b, c, d = rss.channel.title,  
  7.                                            rss.channel.description,  
  8.                                            rss.channel.link,  
  9.                                            rss.channel.copyright  
  10.                             else  
  11.                               atom = f[:feed]  
  12.                               a, b, c, d = atom.title,  
  13.                                            atom.subtitle,  
  14.                                            atom.links.join,  
  15.                                            atom.rights  
  16.                             end  

或者:
ruby 代码
 
  1. params[:feed][:channel],    
  2. params[:feed][:description],    
  3. params[:feed][:link],    
  4. params[:feed][:copyright] = if f[:mode] == "rss"    
  5.                               rss = f[:feed]   
  6.   
  7.                               [rss.channel.title,    
  8.                                rss.channel.description,    
  9.                                rss.channel.link,    
  10.                                rss.channel.copyright]  
  11.                             else    
  12.                               atom = f[:feed]  
  13.     
  14.                               [atom.title,    
  15.                                atom.subtitle,    
  16.                                atom.links.join,    
  17.                                atom.rights]    
  18.                             end    

函数式风格的好处是,如果这一段代码只是为了给某几个变量赋值,那么函数式的写法一目了然,副作用也变得尽在掌控中。而命令式风格则没那个好处了。


说明:
1、并行赋值的重要性主要在于让处理分支中的多变量赋值时方便,否则例二中的情况就很难用函数式风格处理了
2、例二中的a,b,c,d当然可以直接用[]括起来处理(如上),但我用abcd也是为了让代码一目了然,个人感觉而已。
   发表时间:2007-02-06  
确实有意思,能多讲讲ruby的FP编程吗?
0 请登录后投票
   发表时间:2007-02-06  
返回多值其实就是个列表
并行赋值倒是有意思,但这不是FP的一部分
0 请登录后投票
   发表时间:2007-02-06  
    * everything is message(也即任何语句都会返回值)
    * may return multiple values(可以返回多值)
    * parallel assignment(并行赋值
------------------------------------
这些跟FP编程有什么关系?
0 请登录后投票
   发表时间:2007-02-06  
有意思
0 请登录后投票
   发表时间:2007-02-06  
我的意思是函数式“风格”,尤其是我打算用的erlang的风格。
即使没有上面说的几条,python, javascript和groovy等也都可以有各自的函数式风格,但就是不那么直接了当。

比如在javascript里,if-else子句是不能想象成一个函数的,也就是说你不能写:
var a = if (cond())  
          1;
        else
          2;

当然有其它转换成函数形式的方法(不包括var a = cond() ? 1 : 2;),比如象上面那句:
var a = (cond() && 1) || (true && 2);

这种例子很多人写过。


还有就是ruby的begin-end子句,可以直接把一段“括”起来并返回最后一句作为值,这样就可以随手在ruby代码中写一段函数,而不用先定义lambda,然后再call它。比如上面那一段:
   :limit  => begin  
                 limit = par[:m] ? par[:m].to_i : 20  
                 limit >= 4096 ? 4096 : limit  
              end,  



并行赋值和多值返回跟是否FP确实关系不大,但ruby里的这些内建语法让这些东西写起来比较有我喜欢的风格。如果写一个javascript的例子,也不会那么直接了当滴。

有空我会把找到的erlang和ruby之间的一些对应贴出来。
0 请登录后投票
   发表时间:2007-02-06  
begin...end就是个lambda
if cond()那个例子是模式匹配,我只在haskell erlang见过
0 请登录后投票
   发表时间:2007-02-07  
这是另一段代码,是一个直来直去的权限条件判定,返回一个hash给以后的find用:
prvlg_cond = privilege_cond(user, params[:g])
Feed.find(:all, :conditions => [prvlg_cond[:pre], *prvlg_cond[:sub])


有点象erlang了。

  # grp_str: p -> public(0) , u -> user(1), f -> friends(2) 
  def privilege_cond(user, grp_str)
    grp_str ||= 'puf'
    cond = {:pre => "", :sub => []}
    cond = if loggedin?(user)
             frds = grp_str.include?('f') ? user.friends.find(:all) : []
             frd_ids = frds.collect { |frd| frd.friend_id.to_i }
             
             cond = if grp_str.include?('u')
                      {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") + 
                               " user_id  = ? ",
                       :sub => cond[:sub] + [user.id]}
                    else
                      cond
                    end
      
             cond = if grp_str.include?('f') && !frd_ids.empty?
                      {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") + 
                               " user_id in (?) AND privilege in (?) ",
                       :sub => cond[:sub] + [frd_ids, [0, 2]]}
                    else
                      cond
                    end
      
             cond = if grp_str.include?('p')
                      {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") + 
                               " user_id != ? AND privilege  = ? ",
                       :sub => cond[:sub] + [user.id, 0]}
                    else
                       cond
                    end
           else
             {:pre => "privilege = ?",
              :sub => [0]}
           end
  end

0 请登录后投票
   发表时间:2007-02-10  
Javascript if-else的方案:
var a = function() {
        if (cond())    
           return 1;  
        else  
           return 2;
}()

不知道是不是画了一条用脚走路的蛇,另外对性能可能也有影响。
0 请登录后投票
   发表时间:2007-02-14  
dcaoyuan 写道
这是另一段代码,是一个直来直去的权限条件判定,返回一个hash给以后的find用:
prvlg_cond = privilege_cond(user, params[:g])
Feed.find(:all, :conditions => [prvlg_cond[:pre], *prvlg_cond[:sub])


有点象erlang了。

  # grp_str: p -> public(0) , u -> user(1), f -> friends(2) 
  def privilege_cond(user, grp_str)
    grp_str ||= 'puf'
    cond = {:pre => "", :sub => []}
    cond = if loggedin?(user)
             frds = grp_str.include?('f') ? user.friends.find(:all) : []
             frd_ids = frds.collect { |frd| frd.friend_id.to_i }
             
             cond = if grp_str.include?('u')
                      {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") + 
                               " user_id  = ? ",
                       :sub => cond[:sub] + [user.id]}
                    else
                      cond
                    end
      
             cond = if grp_str.include?('f') && !frd_ids.empty?
                      {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") + 
                               " user_id in (?) AND privilege in (?) ",
                       :sub => cond[:sub] + [frd_ids, [0, 2]]}
                    else
                      cond
                    end
      
             cond = if grp_str.include?('p')
                      {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") + 
                               " user_id != ? AND privilege  = ? ",
                       :sub => cond[:sub] + [user.id, 0]}
                    else
                       cond
                    end
           else
             {:pre => "privilege = ?",
              :sub => [0]}
           end
  end



上面的代码感觉有点坏味道,可以重构一下么?
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics