Clojure学习笔记

yinwuzhe 2015-01-13

Clojure官方网站:http://clojure.org/
IntelliJ 插件地址 https://cursiveclojure.com/
Clojure是在JVM上重新实现的Lisp。
Clojure中的并发工具包和数据结构就是一项新技术。并发抽象层让程序员可以写出更加安全的多线程代码。它和Clojure的序列抽象层(对集合和数据结构上的不同看法)相结合,为开发人员提供了非常强大的工具箱。
Clojure认为值才是真正重要的概念。值可以是数字、字符串、向量、映射、集合,或其他任何东西。一旦创建,Clojure的值就不能再变了,因为它们是不可变的。
Clojure处理状态和内存的方式是它在名字和值之间创建了一个关联关系。这就是绑定,通过特殊形式(def)建立。Clojure中的特殊形式相当于Java的关键字。
(def)的句法是:
(def<名称> <值>)
在REPL中可以输入Clojure代码,也可以执行Clojure函数。它是个交互式环境,而且在前面得出的计算结果不会被丢掉。可以用它做探索式编程。
Clojure中没有可变状态,但有可以改变绑定值的符号。Clojure不是让“内存盒子”中的内容改变,而是让符号绑定到不同的不可变值上。在程序的生命期内,var可以指向不同的值。
可变状态和不同绑定两者之间的区别很微妙,但这个概念很重要,一定要掌握。要记住,可变状态是指盒子中的内容变了,而重新绑定是指在不同时间指向不同的盒子。
“定义函数”宏(defn)。宏是类Lisp语言的关键概念之一,其核心思想是内置结构和普通代码之间的区别应该尽可能小。
  • (defn<符号><值?>):把符号绑到值上(如果有的话)。如果有必要创建与符号对应的var。
  • (fn<名称>?[<参数>*]<表达式>*):返回带有特定参数的函数值,并把它们应用到表达式上。通常跟(def)相结合,变成形式(defn)
  • (if<test><then><else>?):如果test的计算结果为true,计算then并产出其结果。否则计算else并产出其结果,当然,前提是else存在。
  • (let[<绑定>*]<表达式>*):给局部名称分配别名值,并隐式定义一个作用域。使得在let作用域内的所有表达式都能获得该别名。
  • (do<表达式>*):按顺序计算表达式的值,并产出最后一个结果
  • (quote<形式>):照原样返回形式(不经计算)。它只能接受一个形式参数,其他的参数全都会被忽略。
  • (var<符号>):返回与符号对应的var(返回一个Clojure JVM对象,不是值)
(quote)以一种特殊的方式处理它的参数。具体来说就是它不会计算参数,所以第一个参数不是函数值也没有问题。
Clojure的向量(vector)跟数组类似,实际上,基本上可以把Clojure列表等同于Java的LinkedList,向量等同于ArrayList。向量可以用方括号表示。
(vec)形式以一个列表为参数,并用这个列表创建向量,而(vector)形式以多个独立符号为参数,并返回包含它们的向量。
函数(nth)有两个参数:集合和索引。它跟Java中的List接口的get()方法类似。可以用在向量和列表上,也可以用在Java集合甚至字符串(字符的集合)上。
Clojure也支持映射(map,相当于Java的HashMap),定义很简单:
{key 1 value1 key2 "value2"}
关于关键字,请记住下面这些知识点:
  • Clojure的关键字是只有一个参数的函数,其参数必须是映射。
  • 在映射上调用这个函数会返回映射里与该关键字函数对应的值。
  • 关键字的使用遵循语法对称性规则,即(my-map:key)和(:key my-map)都是合法的。
  • 关键字作为值使用时返回自身
  • 关键字在使用之前无需声明或def。
  • Clojure中的函数也是值,因此可以放在映射里当键用。
  • 可以用逗号(但没必要)来分隔键值对,因为Clojure会把他们当做空格处理
  • 除了关键字,其他符号也能用在映射里做键,但关键字太好用了们所以我们要特别提出来,你应该把它用在自己的代码中。
除了映射字面值,Clojure还有个(map)函数,它不像(list),(map)函数不会产生映射。而是对集合中的元素轮番应用其中的函数,并用返回的新值建立一个新集合。
Clojure也支持集(set),跟Java的HashSet很像。它的缩写形式是:
#{"apple" "pair" "peach"}
Clojure没有Java里那种意义的操作符,只能用函数:
(add 3 4)
也可以这样写
(+ 3 4)
Clojure的相等形式(相当于Java里的equals()和==)状况稍微有点复杂。Clojure有两个跟相等相关的形式:(=)和(identical?)。注意它们的名字,这全都是因为Clojure不为操作符保留字符。另外,(=)也是等号,而不是赋值符号。
(+)是clojure.core命名空间下的函数,能够接受0到任意数目的参数,假如没有参数,则返回0。
Clojure中有两个值表示逻辑假:false和nil。其他全是逻辑真。
对于VM来说,Clojure函数是实现了clojure.lang.IFn的对象。
Schwartzian转换的基本思想是基于向量中的元素的某些属性對元素进行排序。排序所依据的属性值是通过在元素上调用键控函数确定的。
读取器宏
'     引号,展开为(quote),产生不进行计算的形式
;     注释,标记知道行尾的注释,就像Java里的//
\    字符,产生一个字面字符
@  解引用,展开为(deref),接受var对象并返回对象中的值(跟(var)形式的操作相反)。在事务内存上下文中海油其他含义。
^   元数据,将一个元数据映射到附加对象上。
`     语法引用,经常用在宏定义中的引号形式,不太适合初学者
#    派发,有几种不同的子形式
#'   展开为(var)
#{}  创建一个集字面值
#()  创建匿名函数字面值,用在哪些使用(fn)太啰嗦的地方
#_   跳过下一个形式。可以用#_(....多行...)来创建多行注释
#"<模式>"  创建一个正则表达式(作为java.util.regex.Pattern对象)
在自身的环境内”封装“一些值的函数称为闭包。
序列是Clojure的创新,实际上,用Clojure编程主要就是要思考怎么用序列解决特定的问题。
Clojure与Java中的集合与迭代器相对应的核心概念是序列(sequence),或者简称seq。它基本上是把两个Java类的一些特性集成到一个概念里。这样做的动机有三个:
  • 更强健的迭代器,特别是对于多路算法而言。
  • 不可变能力,可以安全地在函数间传递序列。
  • 实现了懒序列的可能性。
(seq <coll>)       返回一个序列,作为所操作集合的”视图“
(frist <coll>)       返回集合的第一个元素,如有必要,先在其上调用(seq)。如果集合为nil,则返回nil
(rest <coll>)        返回从集合中去掉第一个元素后的到的新序列。如果集合为nil,则返回nil
(seq? <o>)          如果o是一个序列,则返回true(也就是实现了ISeq)
(cons <elt> <coll>)  在集合前面增加新元素,并返回由此得到的序列
(conj <coll> <elt>)   返回将新元素加到合适的一端(向量的尾端和列表的头)的新集合
(every? <pred-fn> <coll>) 如果(pred-fn)对集合中的每个元素都返回逻辑真,则返回true
 
列表是自身的序列,而向量不是。因此从理论上来来说,不能在向量上调用(rest)。而实际上是可以的,因为(rest)在操作向量之前先在其上调用了(seq)。这是序列结构中普遍存在的属性:很多序列函数都会接受比序列更通用的对象,并在开始之前先调用(seq)。
Clojure函数有一个强大的特性,它天生就具备参数数量可变的能力,有时称为函数的变元(arity)。参数数量可变的函数称为变参函数(variadic)。
(defn const-fun-arity1
      ([] 0)
      ([x] 1)
      ([x & more] "more"))中的&表明这是该函数的变参版本。
(defn lenStr [y] (.length (.toString y)))中用到了形式(.toString)和(.length),这都是Java方法,它们是在Clojure对象上调用的。符号开始部分的句号,表示运行时应该在下一个参数上调用该名称的方法,底层是用(.)宏实现的。
所有用(def)或它的变体定义的Clojure值都被放在clojure.lang.Var实例中,它可以承载任何java.lang.Object,所以任何可以在java.lang.Object调用的方法都可以在Clojure值上调用。另外一些跟Java交互的形式是用来调用静态方法的。
(System/getProperty "java.vm.version")
Clojure调用的本质
Clojure中的函数调用实际上是JVM的方法调用。JVM不能保证像Lisp语言(特别是Scheme)通常做的那样优化掉尾递归。JVM上一些其他的Lisp方言觉得它们需要真正的尾递归,因此不准备把Lisp函数调用跟JVM方法调用完全等同起来。而Clojure完全以JVM为平台,甚至不惜违背通常的Lisp实践。
如果你想创建一个新的Java对象并在Clojure中操作它,用(new)形式就可以轻松做到。它还有个备选的缩写形式,在类名之后跟一个句号,可以归结为(.)宏的另一个用法。
Clojure有一个强大的宏(proxy),可以用它来创建扩展Java类(或实现接口)的Clojure对象。
(proxy)的一般形式是:
(proxy [<超类/接口>] [<args> <命名函数的实现>+])
第一个向量参数是这个代理类应该实现的接口。如果这个代理还要扩展Java类(如果可以的话,当然,只能扩展一个Java类),这个类名必须是向量中的第一个元素。
第二个向量参数包含传给超类构造方法的参数。这个向量经常是空的,并且如果(proxy)形式只是实现Java接口的话,那它肯定是空的。
这两个参数之后是一个或多个表示单个方法实现的形式,按接口的要求或超类指定的实现。
Clojure的类型系统跟Java高度一致。Clojure数据结构全是真正的Java集合,都实现了对应接口的所有必须部分。因为接口的可选部分一般都跟修改数据结构有关,而Clojure数据结构不可变,所以一般都没实现。
import clojure.lang.ISeq;
import clojure.lang.StringSeq;
/**
 * Created with IntelliJ IDEA.
 * User: billlee
 * Date: 2015/1/13
 * Time: 16:04
 * To change this template use File | Settings | File Templates.
 */
public class ClojureDemo {
    public static void main(String[] args) {
        ISeq seq= StringSeq.create("football");
        while(seq!=null)
        {
            Object first=seq.first();
            System.out.println("Seq:"+seq+"  ;first:"+first);
            seq=seq.next();
        }
    }
}
 
Clojure的指导思想是默认把线程彼此隔开,这种实现并发安全的办法由来已久。假定”没有共享资源“的基线和采用不可变值使Clojure避开了很多Java所面临的问题,从而可以专注于为并发编程安全地共享状态的方法。
实际上,Clojure用不同的方法实现了不同的并发模型:未来式(future)、并行调用(pcall)、引用形式(ref)和代理(agent)。
第一个也是最明显的一个状态分享办法就是不分享。实际上,我们一直使用的Clojure结构var本质上是不可以共享的。如果两个不同线程继承了名字相同的var,并在线程里重新绑定了它,那绑定只在这些线程内部可见,绝不可能被其他线程共享。
有个简单的函数是(pcalls),可以接受数量可变的零参函数,让它们并发执行。它们在运行时管理的线程池上执行,并返回一个懒序列结果。试图访问序列中的任何还没完成的元素会导致访问线程被阻塞。
ref是Clojure在线程间共享状态的办法。它们基于运行时提供的一个模型,在这个模型中,状态的改变要能被多个线程见到。该模型在符号和值之间引入了一个额外的中间层。也就是说,符号绑定到值得引用上,而不是绑定到值上。这个系统基本上是事务化的,并且由Clojure运行时进行协调。
代理是Clojure中异步的、面向对象消息的并发原语。Clojure代理不是共享状态,而是属于另外一个线程的一点儿状态,但它会从另外一个线程中接收消息(以函数的形式)。
应用到代理上的函数在代理的线程上运行。这个线程是由Clojure运行时管理的,在一个程序员通常无法访问的线程池里。运行时还会保证代理中那些可以被外界看到的值是孤立的和原子的。这就是说用户代码只会见到状态修改之前或者之后的代理值。
涉及到的部分练习的代码:
(ns com.clojure.ClojureDemo)
(def hello (fn [] "Hello world"))
(hello)
;使用java中的toString()结合length()获取字符串长度
(defn lenStr [y] (.length (.toString y)))
(defn schwartz [x f]
      (map #(nth %1 0)
           (sort-by #(nth %1 1)
                    (map #(let [w %1] (list w (f w)))x))))
(schwartz ["sads" "21" "ssssewe" "22323" "223" "s"] lenStr)
'(1 2 3 4 5)
(quote (1 23 45 32 545))
(vector 1 2 3)
(vec '(1 2 3 4 5))
[1 2 3 4 5 4 5 2]
["ssa" 2 "dsadsa" "321" 221]
(nth '(1 2 3 "433" "rewr" "e33") 4)
(def foo {"aaa" "111" "bbb" 22222})
(foo "aaa")
(foo "bbb")
(def martijn {:name "Martijn Verburg",:city "London",:area "Highbury"})
(:name martijn)
(:city martijn)
(def ben {:name "ben Evans",:city "London",:area "Holloway"})
(def authors [ben martijn])
(map ( fn [y] (:name y)))
(map ( fn [y] (:name y)) authors)
(+ 3 4)
(defn add [x y] (+ x y))
(+ 2 3 4 56)
(def list-int '(1 2 3 4))
(def vect-int (vec list-int))
(identical? list-int vect-int)
(defn const-fun1 [y] 1)
(defn iden-fun [y] y)
(defn list-maker-fun [x f]
      (map (fn [z] (let [w z]
                        (list w (f w))))x))
(list-maker-fun ["a"] const-fun1)
(list-maker-fun ["a" "b"] const-fun1)
(list-maker-fun [3 4 65] iden-fun)
(schwartz [33 452 53 42 555] iden-fun)
(defn like-for [counter]
      (loop [ctr counter]
            (println ctr)
            (if (< ctr 10)
              (recur (inc ctr))
              ctr)))
(like-for 100)
(like-for 60)
(like-for 6)
(like-for 1)
(defn like-for [counter]
      (loop [ctr counter]
            (println ctr)
            (if (< ctr 100)
              (recur (inc ctr))
              ctr)))
(like-for 99)
(like-for 1)
(defn adder [constToAdd] #(+ constToAdd %1))
(def plus2 (adder 2))
(plus2 222)
(def plus100 (adder 100))
(plus100 22)
(rest '(1 2 3))
(first '(1 2 43 54))
(rest [1 23 45])
(seq ())
(seq [])
(seq '(1 2 3 4))
(seq [22 1 "re" "fdsw" "fdsf" "fdsffd"])
(cons 1 [2 34 43])
(defn next-big-n [n] (let [new-val (+ 1 n)]
                          (lazy-seq (
                                      cons new-val (next-big-n new-val)
                                           ))))
(defn natural-k [k]
      (concat [k] (next-big-n k)))
(take 10 (natural-k 3))
(defn const-fun-arity1
      ([] 1)
      ([x] 1)
      ([x & more] 1))
(const-fun-arity1 1)
(const-fun-arity1 1 2)
(const-fun-arity1 1 2 3)
(defn const-fun-arity1
      ([] 0)
      ([x] 1)
      ([x & more] "more"))
(const-fun-arity1)
(const-fun-arity1 1 )
(const-fun-arity1 1 2 3)
(System/getProperty "java.vm.version")
(import '(java.util.concurrent CountDownLatch LinkedBlockingQueue))
(def cdl (new CountDownLatch 2))
(def lbq (LinkedBlockingQueue.))
(.getClass "test")
(.getClass 2.3)
(.getClass [12 34 534])
(.getClass '(1 2 3 4))
(.getClass (fn [] "Hello world"))
(import '(java.util.concurrent Executors LinkedBlockingQueue TimeUnit))
(def stpe (Executors/newScheduledThreadPool 2))
(def lbq (LinkedBlockingQueue.))
(def msgRdr (proxy [Runnable] []
                   (run [] (.toString (.poll lbq)))))
(def rdrHndl (.scheduleAtFixedRate stpe msgRdr 10 10 TimeUnit/MILLISECONDS))
(import '(java.util ArrayList LinkedList))
(.getClass (.iterator (ArrayList.)))
(.getClass (.iterator (LinkedList.)))
(def simple-future (future (do (println "hello world Line0")
                               (Thread/sleep 10000)
                               (println "we are the world Line1")
                               (Thread/sleep 10000)
                               (println "do the best Line 2"))))
(defn wait-with-for [limit]
      (let [counter 1]
           (loop [ctr counter]
                 (Thread/sleep 500)
                 (println (str "Ctr=" ctr))
                 (if (< ctr limit)
                   (recur (inc ctr))
                   ctr))))
(defn wait-1 [] (wait-with-for 1))
(defn wait-2 [] (wait-with-for 2))
(defn wait-3 [] (wait-with-for 3))
(def wait-seq (pcalls wait-1 wait-2 wait-3))
(first wait-seq)
(first (next wait-seq))
(defn make-new-acc [account-name opening-balance]
      {:name account-name :bal opening-balance})
(defn loop-and-debit [account]
      (loop [acc account]
            (let [balance (:bal acc) my-name (:name acc)]
                 (Thread/sleep 1)
                 (if (> balance 0)
                   (recur (make-new-acc my-name (dec balance)))
                   acc))))
(loop-and-debit (make-new-acc "Ben" 600))
(defn make-new-acc [account-name opening-balance]
      (ref {:name account-name :bal opening-balance}))
(defn alter-acc [acc new-name new-balance]
      (assoc acc :bal new-balance :name new-name))

(defn loop-and-debit [account]
      (loop [acc account]
            (let [balance (:bal @acc)
                  my-name (:name @acc)]
                 (Thread/sleep 1)
                 (if (> balance 0)
                   (recur (dosync (alter acc alter-acc my-name (dec balance)) acc))
                   acc)
                 )))
(def my-acc (make-new-acc "Ben" 500))
(defn my-loop [] (let [the-acc my-acc]
                      (loop-and-debit the-acc)))
(pcalls my-loop my-loop my-loop my-loop my-loop)
(defn wait-and-log [coll str-to-add]
      (do (Thread/sleep 10000)
          (let [my-coll (conj coll str-to-add)]
               (Thread/sleep 10000)
               (conj my-coll str-to-add))))
(def str-coll (agent []))
(send str-coll wait-and-log "some str")
@str-coll
 
 
 

相关推荐