zoukankan      html  css  js  c++  java
  • clojure的语法糖

    语法糖很多, 就是奔这个“懒” 来用clj的.

    但是,在常见的书里(《Clojure编程》《Clojure编程乐趣2》)都对很多基本语法,用法都介绍不全, 不细。看书看得很累。

    比如《Clojure编程》里 第1章介绍了各种基本语法,但是没有介绍for :when,然后在P138 直接用了

    (for [dx [-1 0 1] dy [-1 0 1] :when (not= 0 dx dy)]
          [(+ dx x) (+  dy y)])

    也没有详细解释。也许不是一个作者写的吧。

    对我这种特别笨的人来说,一下就看不懂了,感觉还是有很多坑。

    必须对照https://clojuredocs.org/   + 自己尝试。

    心得

    写在前面。

    在已经学过《SICP》和用过scheme和hy的基础上,感觉Clojure确实有自己的特点。仍然必须先让自己进入什么都不会的状态,耐心去学,才能真学进去,不然,小问题就能卡住半天。还不容易找到答案。

    1学Clojure就像是学汉字。要从边旁部首学起

    和只有7个关键字scheme不同,Clojure系各种语法糖特别多,而且因为引入的不同的集合#{} {} [] '(),客观上带来了很多scheme里没有的“转型"问题。

    如果不从细微出入手,别说读懂句子,就连每个词,每个字,甚至每个部首,都不认识。>_<!

    开始学偏旁部首,认字过程 学的非常慢,都是非常细节,功能单一但是非常细腻的小函数,小动词。

    然后用这些部首自己去造字组词(DSL),然后再去用DSL组词造句表达。

    2  数据结构、类型+远多于其他语言,利弊互现。

    scheme里一切皆列表 list()  顶多知道cons 也就差不多了。

    数据类型,顶多知道有symbol类型 就已经可以写出个解释器了。

    hy/py  也就是py里的set {}  list []  map {k:v}  然后tuple() generator () ...大概也就差不多了

    但是Clojure的数据结构因为 不可变/可变 的分野,几乎等于类型数量×2。

    即使不考虑这一点,其他性能、实现方面的复杂度,工程上和JVM的兼容等等

    Clojure放弃了scheme里一切皆括号()的写法,  引入{} [] 。客观上放眼望去,不是无尽的括号,可读性增强。

    但是弊端当然就是学习、写码时负载增加。

    典型体现在:

    1必须认真理解() 和 [] 的差异,包括conj行为差异,遍历性能差异等等等。

    2 必须记住(for [])  返回的是个list ()  需要别的类型,还要转。

      想直接返回1个list 是麻烦的,这样写是不行的

    [for [l [1 2 3]] (* l 2)]

    因为外层不能是方括号

    而在py里 list 表达式根深蒂固。而hy里,我们有(lfor  )  也能直接返回list。个人认为hy的lfor方案很好。

     

    3 不得不依赖 引入各种转型  比如filter 之外,还搞出了filterv filterm 分别对应返回结果是 vector map 的情况。

    个人感觉,这里还不如py优雅。

    再比如,合并([]  []  [])-> [] nested vector成为1个vector  

    大概只能

    (vec (apply concat nested_coll))
    1 apply concat 把([e1] [e2 e3]) 拼接成(e1 e2 e3),
    2 然后vec把外层()变成[], 得到 [e1 e2 e3]

    虽然把这一行自己写成宏也没什么,但是总感觉怪异,不优雅。

    -----------------------------------------分割线---------------------------------------------------------------

     下面全都是我记录下在py hy里没有的,我这种菜鸟新手不太习惯的写法。陆续增加。

    for和doseq

    上面这句里, for可以同时循环dx  dy 2个变量  相当于2个for嵌套

    后面的:when 保证只有when成立时再执行body

    注意 如果执行函数,要用这个,而不是for

    (doseq [x [0 1]]
    (println "aa")
    )

    这里doseq 换成for 里面的println 是不会被执行的。这里区分有点细,和py的list表达式和 hy区别

    not=

    可以接受多个参数,来判断连续相等。相当于+ - * / 连加

     set 作为谓词

    因为集合可以作为函数,所以当然可以作为谓词函数

    (if (#{3 2}, 0) "" "")

    这个集合#{3 2}完全可以定义为1个谓词函数,就像《Clojure编程》P143一样,在body里把集合定义成谓词,然后在外面简单把集合传进来

    内部

    (if (survive? 0) "" "")

    其实survive?这个看起来是谓词函数的东西,只是个简单的#{2 3}

    mapcat

    (mapcat f p)    等于(concat  (map  f  p)) 把map的结果连接起来

    apply map

    从scheme,py pandas里就都有。但总记不住

    (apply f [p1 p2 p3])

    (map f [p1 p2 p3])

    相同点:  都是紧跟1个函数。后面是一串参数。

    不同点:map: 是“映射”,所以返回((f p1) (f p2) (f p2))

        apply  的f 是接受3个参数的, 返回(f p1 p2 p3)  比如

    (apply + [1 2 3]) 
    =>6
    (map #(* % 2) [1 2 3])
    =>(2 4 6)

    apply还有一个重要作用就是“脱括号”的作用。比如 当参数是[[], []] 这种时,想用concat 把nest2个vector连接起来,用

    (apply concat [vec1, vec2])

    相当于(concat vec1 vec2)

    ——微吐槽:不如py的 list extend()  或者*解引用 itertools.chain(*[vec1 vec2])

    disj

    把元素添加进set

    user=> (disj #{1 2 3}) ; disjoin nothing 
    #{1 2 3} 
    
    user=> (disj #{1 2 3} 2) ; disjoin 2
    #{1 3} 
    
    user=> (disj #{1 2 3} 4) ; disjoin non-existent item
    #{1 2 3} 
    
    user=> (disj #{1 2 3} 1 3) ; disjoin several items at once
    #{2}

    juxt

    和map相同点:都是元素级操作

    不同点:

    map:1个函数,多个参数;

    juxt多个函数,1个参数。

    ((juxt a b c) x) => [(a x) (b x) (c x)]

     constantly

    返回1个函数,这个函数可以接收任意数量的参数,但永远返回初始给定的返回值

    user=> (def boring (constantly 10))
    #'user/boring
    
    user=> (boring 1 2 3)
    10
    
    user=> (boring)
    10
    
    user=> (boring "Is anybody home?")
    10

     Zipper

     来自《FUNCTIONAL PEARL》中的概念。

    以不可变的方式遍历层次数据结构(如嵌套的vector  XML 等等等)。

    参考

    http://josf.info/blog/2014/03/21/getting-acquainted-with-clojure-zippers/

    http://www.thattommyhall.com/2013/08/23/genetic-programming-in-clojure-with-zippers/

     (zipper branch? children make-node root) ;
    ;Creates a new zipper structure. ;
    ;branch? is a fn that, given a node, returns true if can have ;
    ; children, even if it currently doesn't. ;; children is a fn that, given a branch node, returns a seq of its ;; children. ;
    ; make-node is a fn that, given an existing node and a seq of ;; children, returns a new branch node with the supplied children. ;
    ; root is the root node.

    concat和into

    类似但不同。主要是返回值

    into

    (into [1 2] [3 4])
    =>[1 2 3 4]

    concat

    (concat [1 2] [3 4])
    =>(1 2 3 4)

    选入进新的collection

    user=> (into (sorted-map) [ [:a 1] [:c 3] [:b 2] ] )
    {:a 1, :b 2, :c 3}
    user=> (into (sorted-map) [ {:a 1} {:c 3} {:b 2} ] )
    {:a 1, :b 2, :c 3}
    
    ; When maps are the input source, they convert into an unordered sequence 
    ; of key-value pairs, encoded as 2-vectors
    user=> (into [] {1 2, 3 4})
    [[1 2] [3 4]]
    user=> (into () '(1 2 3))
    (3 2 1)
    
    ; This does not happen for a vector, however, due to the behavior of conj:
    user=> (into [1 2 3] '(4 5 6))
    [1 2 3 4 5 6]

    merge-with

    多个map规约为1个map  把每个map的 value 按 f进行规约

    (merge-with f & maps)

    (merge-with into
          {"Lisp" ["Common Lisp" "Clojure"]
           "ML" ["Caml" "Objective Caml"]}
          {"Lisp" ["Scheme"]
           "ML" ["Standard ML"]})
    ;;=> {"Lisp" ["Common Lisp" "Clojure" "Scheme"], "ML" ["Caml" "Objective Caml" "Standard ML"]}
    (merge-with + 
               {:a 1  :b 2}
               {:a 9  :b 98  :c 0}
               {:a 10 :b 100 :c 10}
               {:a 5}
               {:c 5  :d 42})
        
    ;;=> {:d 42, :c 15, :a 25, :b 200}

    assoc

    可以把多个key value 放进一个map里, 把map作为可变对象

    (assoc map key val)
    (assoc map key val & kvs)
    
    (assoc {} :key1 "value" :key2 "another value")
    
    ;;=> {:key2 "another value", :key1 "value"}

    conj

    把多个element 加入conj到coll。 返回新coll,不可变对象

    注意,只保证加入。顺序性各有不同:

    map和set没有顺序不谈

    [] vector在末尾添加

    '() 列表在头部添加

    (conj coll x)
    
    (conj coll x & xs)
    
    ;; notice that conjoining to a vector is done at the end
    (conj [1 2 3] 4)
    ;;=> [1 2 3 4]
    
    ;; notice conjoining to a list is done at the beginning
    (conj '(1 2 3) 4)
    ;;=> (4 1 2 3)
    
    (conj ["a" "b" "c"] "d")
    ;;=> ["a" "b" "c" "d"]
    
    ;; conjoining multiple items is done in order
    (conj [1 2] 3 4)               
    ;;=> [1 2 3 4]
    
    (conj '(1 2) 3 4)               
    ;;=> (4 3 1 2)
    
    (conj [[1 2] [3 4]] [5 6])       
    ;;=> [[1 2] [3 4] [5 6]]

     condp

    接受1个双参数的pred函数,然后跟1个作为第2个参数,

    后面跟的列表,是第1个参数的列表,和值的列表。

    (println (condp #(%1 %2) :foo
      string? "it's a string"
      keyword? "it's a keyword"
      symbol? "it's a symbol"
      fn? "it's a function"
      "something else!")
    )
    =>it's a keyword
    (condp #(%1 2 %2) 3
           = "eq"
           < "lt"
           > "gt")

    vector和vec 

     vector在外面加一层方括号[]

    vec把外层转换成[]

    (vector '(1 2 3))
    =>[(1 2 3)]
    
    (vec '(1 2 3))
    =>[1 2 3]

     超强的let

    es6里let是和const并列的;let定义可变变量,const定义不可变变量。

    Clojure里切不可望文生义。let远不止是用于 创建临时变量 和es6 ts里 的let感觉完全不一样。 

    Clojure里 let和def的区别是这样分的

    let创建 内部不可变变量

    def创建 namespace可变变量

    let的作用起码有3条:

    1 创建内部、不可变变量。  

    2 解构赋值 destruct

    3 赋值有顺序,后面的语句可以调用前面的,所以可以在let中放置顺序执行语句

    (let [var1 v1
          var2  (f1  var1)
          ]
       (f2 var1 var2)
    )

    这样,在let 的方括号里 [] 把var1 var2 按顺序赋值好,其中var2 的赋值还用到了刚刚赋值的var1。

    最后let的body里,只表现最终返回结果就好了。

    可以认为,多用let少用do就对了

    (let [数值准备] 
        (返回的结果)
    )

    只把最后1次计算 方在body里,或者返回 一个map{} 或者list [], 可以突显返回值

    这种涉及思路,相当于把全部内部变量全都在头部声明、赋值1次。只用1个let  避免其他语言里到处const var好多次。

    而且用机制保证了赋值后的不可变性。

    最后,让body聚焦于值的返回

    确实是很强大,很有力的表达方式,一定要掌握。

    fn内部的letfn

    和py/hy不同 defn 定义的一律是namespace级别的。所以如果defn内部嵌入1个defn定义,则外部执行2遍,将导致内部defn也更新。(因为def可以覆盖),但本意内部的defn是外部不可见的局部函数。这时就用letfn

    简单说,任何涉及fn内部的东西,都要显式用let/letfn来构造。

    在Clojure里, 局部/ns  的区别必须显式声明!

     for中的:when和:while 

    (for [x (range 20) :when (not= x 10)] x)
    ; =>(0 1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 18 19)
    
    (for [x (range 20) :while (not= x 10)] x)
    ; => (0 1 2 3 4 5 6 7 8 9)

    :when会遍历整个循环,条件不满足的不执行   类似 continue

    :while 遇到第一次满足条件的地方,就会退出。类似break

    filter

    只相当于for中的:when,不能在第1次满足是停下:下面3种写法是等效的

    (for [x (range 20) :when (not= x 10)] x)
    (filter #(not= % 10) (for [x (range 20)] x))
    (filter #(not= % 10) (range 20))

    后两种显然最后更简单。

    不同点:

    写在 :when和:while 里面的不是函数,是表达式 

    如何选择:

    如果过滤条件简单,就是个简单表达式,那么用for更合适;

    如果过滤条件是外部定义的predicator函数,那么用filter合适;

    个人倾向,尽量使用filter/filterv/filterm,不显式使用for和循环,尤其coll已经是外部赋值好的时候。 和需要定制返回值类型时

    for的好处是,可以在body里对返回值进行定制。

    字符串分割

    #"" 是正则表达式 如果只用1个分割符,就这样

    (str/split "*-D1R2" #"-")

    等价于py里

    "*-D1R2".split("-")

    identity和constantly

    都是用于创建简单函数: 
    identity返回f(x)=x
     
    而constantly 返回 f(x)=const  不论接收到什么参数,永远返回创建时给的const
    user=> (def boring (constantly 10))
    #'user/boring
    
    user=> (boring 1 2 3)
    10
    
    user=> (boring)
    10
    
    user=> (boring "Is anybody home?")
    10

     loop recur的坑

    注意recur里,各循环变量还是不可变的

    不要循环多个变量。特别是互相依赖的时候。
    (loop [iter 1
           acc  0]
      (if (> iter 10)
        (println acc)
        (recur (inc iter) (+ acc iter))))
    
    ;; => 55
    ;; sum from 1 to 10

    每次recur (inc i)  ( XX i)

    第2个里面不能立即用inc后的(inc iter)后的iter值
    在这里,这种机制保证了结果的正确:
    在最后1次loop的时候(iter=10),虽然第一个语句(inc iter)似乎把iter加到了11, 但是第2个语句里(+ acc iter)仍然把iter=10送进去累加,然后跳到头部 遇到if 打印了最终的acc。
     
    所以,recur的两次调用,用的iter在前后语句里不会改变! 可以认为loop声明的变量还是类似let,对开发者的代码是不可变的;而iter值的改变在代码recur执行完,goto过程中,才由Clojure完成
     
    只好改成里面嵌套let了
          (loop [i 0]
            (let [res1 ((get fns-cmp i) env1 env2)]
              (if (or (>= i n) (not= 0 res1))
                res1
                (recur (inc i))
              )
            )
          )

    update-in

    修改嵌套的数据结构内的值,有点类似mongo的查询条件,但是简单得多
    (def users [{:name "James" :age 26}  {:name "John" :age 43}])
    #'user/users
    
    ;; similar to assoc-in but does not simply replace the item.
    ;; the specified function is performed on the matching item.
    ;; here the age of the second (index 1) user is incremented.
    (update-in users [1 :age] inc)
    ;;=> [{:name "James", :age 26} {:name "John", :age 44}]

     查询条件是nest指向1个记录的,所以如果想更新多于1个值,需要assoc

    ;;You can use update-in in a nested map too, in order to update more than
    ;;one value:
    
    (def m {:1 {:value 0, :active false}, :2 {:value 0, :active false}})
    
    (update-in m [:1] assoc :value 1 :active true)
    ;;=>{:1 {:value 1, :active true}, :2 {:value 0, :active false}}
     
     
     
  • 相关阅读:
    GridView只显示日期问题
    自定义一个选择日期的用户控件
    母版页所带来的路径问题
    C#之旅(一): 泛型 和IComparable、IComparer
    使用HttpWebRequest来秒杀
    NameValueCollection Dictionary区别
    在C#中使用代理的方式触发事件 (委托和事件 )(二)(转)
    SQL2005语句实现行转列,列转行
    值类型和引用类型的区别?(转)
    2010年年终总结
  • 原文地址:https://www.cnblogs.com/xuanmanstein/p/10893199.html
Copyright © 2011-2022 走看看