zoukankan      html  css  js  c++  java
  • 《实用commonlisp编程》对js编码的一点启发

    《实用common-lisp编程》对js编码的一点启发

    1 lisp与javascript

    javascript是披着lisp外衣的C,所以就去看了眼lisp。由田春翻译的《实用common-lisp编程》 javascript能在函数调用的时候,指定上下文this,当看到common-lisp中的funcall和funapply的时候,只从名字上看,感到格外亲切

    2 函数式编程带来的效果

    如果一种编程概念或方法论,它在确实更好的解决了问题,那么它就是好的。这里看到lisp写的数据查询函数我确实被震撼了。

    2.1 唱片数据库——《common lisp 实用编程》例子

    一组放在内存中的唱片数据库*db*

    (defvar *db* nil) ;; 定义一个全局变量  星号是lisp全局变量名约定
    
    ;; 创建cd函数
    (defun makcd (title artist rating ripped)
      (list :title title :artist artist :rating rating :ripped ripped))
    
    ;; 添加唱片数据函数
    (defun add-record(cd)
      (push cd *db*))
    
    (add-record (make-cd "Roses" "Kathy Mattea" 7 t))  
    (add-record (make-cd "Fly" "Dixie Chicks" 8 t))  
    
    ;; 现在*db*看起来是这样的
    ((:TITLE "Roses" :ARTIEST "Kathy Mattea" 7 t)
     (:TITLE "Fly" :ARTIEST "Dixie Chicks" 8 t))
    
    

    添加了很多数据之后……

    2.2 如何查询?

    先看一个函数

    (remove-if-not #'evenp '(1 2 3 4 5 6 7 8 9 10))
    ;; -> (2 4 6 8 10)
    

    函数evenp是一个过滤函数:当其参数为偶数,返回真。也可以写成传入一个匿名函数作为过滤器

    (remove-if-not #(lambda (x) (= 0 (mod x 2))) '(1 2 3 4 5 6 7 8 9 10))
    ;; -> (2 4 6 8 10)
    

    js与之对比的函数便是

    [1,2,3,4,5,6,7,8,9].filter(function(n){
    return n%2 == 0 ;
    })
    

    有了remove-if-not这样一个过滤函数,回到上面的数据查询,其实就是添加一个过滤器,比如查询*db*中artist为 "Dixie Chicks"的数据,lisp风格的代码,更上面的偶数过滤函数类似,封装在一个lambda匿名函数里

    (defun select-by-artist (artist)
      (remove-if-not
       #'(lambda (cd) (equal (getf cd :artist) artist)) *db*))
    

    现在就可以调用

    (select-by-artist "Dixie Chicks")
    

    来获取某个artist为"Dixie Chicks"的数据了。 值得注意的是,上面的函数只能查询artist关键词,如果你要查询其它关键词,比如评分rating为7的,类似的你还要写一系列 类似与select-by-artist的函数,select-by-title select-by-rating etc. 于是,可以写一个通用的函数,接受一个函数作为参数

    (defun select (selector-fn)
      (remove-if-not slector-fn *db*))
    

    select-by-artist函数中的匿名函数可以抽离出来

    (defun artist-selector (artist)
      #'(lambda (cd) (equal (getf cd :artist) artist)))
    

    那么这样形式的调用

    (select-by-artist "Dixie Chicks")
    

    就变为

    (select (artist-selector "Dixie Chicks"))
    

    类似的,可以写一个 title-selector

    (defun title-selector (title)
      #'(lambda (cd) (equal (getf cd :title) title)))
    

    可以查询title了

    (select (title-selector "Fly"))
    

    但是,若需要查询的字段很多,岂不是要傻傻的写很多xxx-selector? 能不能写一个函数,动态生成函数:根据传入的参数,生成对应参数过滤的函数 比如传入title,就生成一个title-selector,若传入artist就生成artist-selector 两者都传入,那么就是一个and的关系 lisp 写出这样代码非常自然,虽然看起来有点晕~ 有了这样的函数,那么查询操作看起来就是这样的

    (select (where :title "Fly" :artist "Dixie Chicks"))
    

    where即是需要实现的函数——如果你了解过SQL语句的话,你就知道为什么叫where了

    (defun where (&key title artist rating)
      #'(lambda (cd)
          (and
           (if title (equal (getf cd :title) title) t)
           (if artist (equal (getf cd :artist) artist) t)
           (if rating (equal (getf cd :rating) rating) t))))
    

    不纠结与语法细节,单从消除重复这样的效果上看,给人印象是很深刻的,一个where函数就干掉了众多的xxx-selector

    (select (where :title "Fly"))
    

    就相当于

    (select (title-selector "Fly"))
    

    显然前者非常通用

    2.3 对应js实现

    函数是第一类型的,函数可以作为参数。js也是具备这样的特性的 js在实现where这样的功能时,其表现力如何?我试着实现了下

    2.3.1 数组作为数据库

    var db = [];
    
    function makecd(title,artist,rating,ripped){
        return {title:title,artist:artist,rating:rating,ripped:ripped};
    }
    
    function addRecord(makecd,title,artist,rating,ripped){
        db.push(makecd(title,artist,rating,ripped));
    }
    
    addRecord(makecd,"Roses","Kathy Mattea",7,true)
    addRecord(makecd,"Fly","Dixie Chicks",8,true);
    
    /* 现在db中的数据是:
       [ { title: 'Roses',artist: 'Kathy Mattea',rating: 7,ripped: true },
       { title: 'Fly', artist: 'Dixie Chicks', rating: 8, ripped: true } ]
    */
    

    2.3.2 查询操作

    js的filter函数和lisp的remove-if-not 对比两种实现 lisp way:

    (defun select-by-artist (artist)
      (remove-if-not
       #'(lambda (cd) (equal (getf cd :artist) artist)) *db*))
    

    javascript way:

    function selectByArtist(artist){
      return db.filter(function(cd){
               return cd.artist == artist;
             });
    }
    
    /*
    console.log(selectByArtist('Kathy Mattea'));
    [ { title: 'Roses', artist: 'Kathy Mattea', rating: 7, ripped: true } ]
    */
    

    为避免写很多的判断,比如上面如果还要判断title,那么js的实现,还需要加上

    function selectByArtist(artist,title){
      return db.filter(function(cd){
               return cd.artist == artist && cd.title == title;
             });
    }
    

    可是,title是可选的啊,在进行 && 操作前还的判断 title传进来没有.这里的

    function(cd){
        return cd.artist == artist && cd.title == title;
    }
    

    相当于

    (defun artist-selector (artist)
      #'(lambda (cd) (equal (getf cd :artist) artist)))
    

    不够通用,就像lisp那样,实现一个where条件过滤器

    /**
     * 接受一组函数和参数
     * 当所有的函数对参数的运行都为真时,返回真,否这返回假
     */
    function all(){
        var fns = [].slice.call(arguments),
        len = fns.length;
        return function(){
            var i=0,
            fn
            while(fn = fns[i]){
                i++;
                if(!fn.apply(null,arguments)){
                    return false;
                }
            }
            return true;
        };
    }
    

    这是一个工具函数,以任意个函数做为参数,返回一个合并后的函数,只有这任意个函数的值为真,这个合并函数的值才为真 函数作为参数传递,函数的返回值也是一个函数 下面的演示

    var retfn = all(
    function(n){
      return n>0;
    },
    function(n){
      return n<10}
    );
    var ret = [11,-1,5,4,9,11];
    ret = ret.filter(retfn);
    console.log(ret);
    ->[ 5, 4, 9 ]
    

    出于演示的目的,把本可以放在一起判断的逻辑

    ret.filter(function(n){
        return n>0 && n<10;
    })
    

    拆成了两个函数

    2.3.3 查询函数生成器

    上面lisp 实现的 where 函数可以根据参数, (&key title artist rating) 生成一个合并后的查询函数 像(&key title artist rating)这样的带有名字的参数列表,在js中可以用对象{key1:val1,key2:val2} 来模拟

    /**
     * 查询函数生成器
     */
    function makequery(o){
        var fns = [];
        for(var x in o){
            fns.push(function(cd){
                return o[x] == cd[x]
            });
        }
        return fns;
    }
    
    /*
      var allquery = makequery({rating:8});
    
      var allfn = all.apply(null,allquery);
    
      var queryresult = db.filter(allfn);
    
      console.log(queryresult);
    */
    

    比如,makequery({rating:8})返回一个函数,这个函数当传入对象的rating字段为8时,返回真,否则返回假 若传入两个字段,则返回包含两个函数的数组。 工具函数all能合并多个过滤函数为一个,这样,js版本的where函数也就大功告成了

    /*
     * 合并上面的得到
     */
    function where(o){
        return db.filter(all.apply(null,makequery(o)));
    }
    
    /**
       var ret = where({rating:8})
       console.log(ret);
    */
    

    select功能实现后,顺带也把update功能也实现

    /**
     * 更新操作,希望的接口是这样的
     * update(db,where({artist:'Dixie Chicks'}),{rating:8.5})
     */
    function update(db,queryresult,to){
        queryresult.forEach(function(item){
            var idx = db.indexOf(item);
            db.splice(idx,1,merge(item,to));
        });
    }
    
    function merge(original,extra){
        for(var x in extra){
            original[x]  = extra [x];
        }
        return original;
    }
    
    /*
      update(db,where({artist:'Dixie Chicks'}),{rating:8.5});
      console.log(db);
    */
    
    function deldb(db,queryresult){
        queryresult.forEach(function(item){
            var idx = db.indexOf(item);
            db.splice(idx,1);
        });
    }
    
    /*
      deldb(db,where({artist:'Dixie Chicks'}));
      console.log(db);
    */
    

    3 总结

    学了一点lisp,就想试着折腾下函数,用函数生成函数,也是重用代码,减少冗余的有效方法

    Author: tom

    Date: 2012-08-20 23:22:11 CST

    HTML generated by org-mode 6.33x in emacs 23

  • 相关阅读:
    memcached全面剖析
    Zabbix中文使用手册
    lombok
    guava cache
    linux 文件检索操作
    mysql慢查询
    碎片脚本注解(后续整理)
    Docker 目录挂载详述
    jenkins 添加 sonraqube java&vue项目记录
    Ansible unarchive模块
  • 原文地址:https://www.cnblogs.com/wewe/p/2648327.html
Copyright © 2011-2022 走看看