The Joy of Clojure
目录 |
基础
- (for [x [:a :b], y (range 5) :when (odd? y)] [x y])
- for产生lazy seq
- :when :while是guard's
- Simplicity, freedom to focus, empowerment, consistency, and clarity.
- LISP: atom, car, cdr, cond, cons, eq, quote; lambda, label
- SQL DSL???
- ~:unquote
- Code is data
- FP?:Haskell, ML, Factor, Unlambda, Ruby, or Qi.
- OOP:no clear distinction between state and identity(状态 vs. 身份)
- defprotocol(定义接口)、extend-type(扩展类型)
- 关键字::keyword
- 容器类型:
- Lists:(yankee hotel foxtrot)
- 第一个元素:A form is any Clojure object meant to be evaluated ...
- Vectors:[1 2 :a :b :c]
- Maps(这里的语法很诡异):{1 "one", 2 "two", 3 "three"}
- Sets:#{1 2 "three" :four 0x5}
- Lists:(yankee hotel foxtrot)
- 函数调用
- (+ 1 2 3)
- 定义函数
- (fn mk-set [x y] #{x y})
- ((fn [x y] #{x y}) 1 2) --匿名函数?
- 允许arity重载:(fn ([x] #{x}) ([x y] #{x y}))
- 变长参数:((fn arity2+ [x y & z] [x y z]) 1 2 3 4) ;=> [1 2 (3 4)] &后的参数以list传递
- 允许arity重载:(fn ([x] #{x}) ([x y] #{x y}))
- ((fn [x y] #{x y}) 1 2) --匿名函数?
- (fn mk-set [x y] #{x y})
- special form:
- 匿名函数?:fn
- 命名函数:def defn
- 顺序执行:do
- 尾递归:recur
- 条件:if(when,and,or是宏)
- 循环:loop(作为recur的跳转目标)。。。恩,recur相当于C语言里的goto了
- let(局部变量与`locals`有什么区别?):immutable
- (def make-a-set (fn ([x] #{x}) ([x y] #{x y}))) 相当于JavaScript里的var f = function(){ ... }
- In-place functions with #() 这里的记号似乎有点混淆?
- (def make-a-list_ #(list %)) ;为什么不是defn?
- (def make-a-list3+ #(list %1 %2 %3 %&))
- Vars
- (def x 42) ;这是root binding(全局变量?)
- 测试:(.start (Thread. #(println "Answer: " x)))
- Locals, loops, and blocks
- (let [x 1 y 2] (println (+ x y)))
- ...
- Preventing things from happening: quoting
- (cons 1 [2 3]) ;立即评估
- => (quote (cons 1 [2 3])) ;推迟评估
- (cons 1 (quote (2 3))) ???(评估过程对于LISP表达式是递归的函数调用吗?难怪没法区分)=> (cons 1 '(2 3))
- Syntax-quote:`(1 2 3) ;用于动态构造函数代码?
- clojure.core/map
- `能够做自动名字空间解开:`(map even? [1 2 3]) ;重名的情况怎么办? ... "will use the current namespace"
- unquote ~
- `(+ 10 ~(* 3 2))
- unquote-splicing(FP里的评估、bound/unbound概念真是麻烦!)
- (let [x '(2 3)] `(1 ~@x))
- Auto unqualified symbol:`potion#
- Java Interop(Java互操作)
- 静态方法调用:(Math/sqrt 9)
- 类实例:(new java.util.HashMap {"foo" 42 "bar" 9 "baz" "quux"})
- 或(Clojure习俗):(java.util.HashMap. {"foo" 42 "bar" 9 "baz" "quux"})
- (.x (java.awt.Point. 10 20)) ;这相当于把一个成员方法作用于一个对象实例(函数调用的风格)
- (let [origin (java.awt.Point. 0 0)] (set! (.x origin) 15) ) ;这里的set!是通过反射调用的吗
- 调用链风格:..(Clojure里更习惯用->、->>)
- (.. (java.util.Date.) toString (endsWith "2010"))
- doto宏
- macros
- reify、deftype:创建Java接口的实现
- ns:名字空间
- (ns joy.req (:require clojure.set)) ;暂时无法理解这么做的原因?
- (ns joy.use-ex (:use [clojure.string :only [capitalize]])) ;仅将capitalize映射到当前名字空间。。。哦。。。
- :refer *
- (ns joy.java (:import [java.util HashMap] [java.util.concurrent.atomic AtomicLong]))
- 异常(暂略)
- 当发生异常时,(.printStackTrace *e)
- Truthiness
- []、()不同于nil
- 不要创建Boolean类型的对象!
- nil punning
- Destructuring(相当于Scala/Erlang里的模式匹配)
- e.g. (let [[f-name m-name l-name] guys-whole-name] (str l-name ", " f-name " " m-name))
- e.g. (let [[a b c & more] (range 10)] ... )
- 匹配Map:(let [{f-name :f-name, m-name :m-name, l-name :l-name} guys-name-map]
- associative destructuring???
- (let [{first-thing 0, last-thing 3} [1 2 3 4]] [first-thing last-thing])
- (for [x (range 2) y (range 2)] [x y]) ;这个2重循环的写法真怪异
Data types
- M后缀:任意精度。(let [imadeuapi 3.14159265358979323846264338327950288419716939937M] ...
- Promotion(自动的精度提升):如何检测‘溢出’的?(class (+ clueless 90000000000000000000))
- Rounding errors
- 表达式中有一个是double,结果就是double???(+ 0.1M 0.1M 0.1M 0.1 0.1M 0.1M 0.1M 0.1M 0.1M 0.1M)
- 使用有理数?
- BigDecimal用一个32位数表示小数点后的位数,。。。
- 浮点数不满足结合律于分配律(st!)
- (def b (rationalize -1.0e50))
- keywords vs. symbols
- (identical? 'goat 'goat) 与 (= 'goat 'goat)
- 为什么不把相同的symbols存储到同一个'位置'?... metadata
- (let [x (with-meta 'goat {:ornery true}) y (with-meta 'goat {:ornery false})] ...
- 为什么不把相同的symbols存储到同一个'位置'?... metadata
- (identical? 'goat 'goat) 与 (= 'goat 'goat)
- Lisp-1:uses the same name resolution for function and value bindings,name-shadowing问题
- vs Lisp-2:依赖于context
- (defn best [f xs] (reduce #(if (f % %2) % %2) xs)) ~~~寒
- 正则表达式:#"an example pattern"
- 区别:(java.util.regex.Pattern/compile "\\d")等于#"\d"
- Regex flags:d i(不区分大小写) x m s u,例如:#"(?i)yo"
- (seq (.split #"," "one,two,three"))与(re-seq #"\w+" "one-two/three")
- !!!不要使用Matcher对象(re-matcher)
- Clojure’s sequence abstraction — all the persistent collections use it
- `persistent`:所有历史版本保留
- (def ds [:willie :barnabas :adam]) ==>修改:(def ds1 (replace {:barnabas :quentin} ds)) ;Scala风格
- 变成可变的:(def ds (into-array [:willie :barnabas :adam])) ==>修改:(aset ds 1 :quentin)
- (def ds [:willie :barnabas :adam]) ==>修改:(def ds1 (replace {:barnabas :quentin} ds)) ;Scala风格
- 比较sequential, sequence, and seq:
- sequential:元素顺序不会reordering
- sequence:seq操作返回,可在上执行first、rest操作(这里的命名与Lisp不太一致)
- (= [1 2 3] '(1 2 3))返回true,因为=操作基于序列接口?
- Vectors:immutable and persistent(arrays可变)
- (let [my-vector [:a :b :c]] (into my-vector (range 10))) ;‘倒进’操作 -- 这些名字太难记了!
- 相对与Lists更有效率的地方:从右边添加/删除元素;按索引访问;反向遍历
- (def a-to-j (vec (map char (range 65 75)))):
- (nth a-to-j 4) ;vector空或index越界时返回nil
- (get a-to-j 4) ;index越界时抛出异常
- (a-to-j 4) ;会抛出异常(这里用法类似于Pyhton/C++?)
- 多维索引访问:get-in assoc-in update-in
- Vectors as stacks:conj/pop(靠,这个命名。。。);clojure.lang.IPersistentStack
- 经典的Lisp总是要求list翻转操作,但idiomatic Clojure使用vector就不需要(-_-)
- Subvectors:subvec操作,参数意义:[start, end)
- 作为MapEntry使用(实际上就是C++ hashmap的pair<K,V>)
- Lists: Clojure’s code form data structure
- (cons 1 '(2 3))与(conj '(2 3) 1)
- Clojure has no “dotted pair.” If you don’t know what that is, don’t worry about it.(这话真操蛋)
- peek/pop粗糙地等于first/rest(靠,作者似乎对程序的操作语义极为关注。。。但是一般的使用很容易混淆。。。)
- contains? will always return false for a list. ;既然不支持set接口,干吗要定义这个操作?
- 队列
- not a workflow mechanism(java.util.concurrent.BlockingQueue)
- clojure.lang.PersistentQueue/EMPTY
- 内部实现:the front being a seq and the rear being a vector(类似于一个C++ rope?)
- Persistent sets
- (sorted-set :b :c :a)
- Sets are implemented as maps with the same element as the key and value,。。。
- Thinking in maps
- key,value不要求同一类型:(let [m {:a 1, 1 :b, [1 2 3] "4 5 6"}] ...
- (map,key)可以视为函数调用。。。
- (into {} [[:a 1] [:b 2]]) ;返回一个相同结构的map对象
- (apply hash-map [:a 1 :b 2]) ;=> {:a 1, :b 2}
- (sorted-map :thx 1138 :r2d 2) ;支持key的有序遍历?(干吗不定义tree数据结构呢)
- 按照插入顺序:(seq (array-map :a 1, :b 2, :c 3))
- It’s usually a bad idea to build your programs around concrete types, and always bad to build around undocumented behaviors.
FP
- Equality in the presence of mutability has no meaning.
- And if two objects aren’t equal forever, then they’re technically never equal (Baker 1993). 哈哈
- lazy-seq原则:
- 使用lazy-seq宏
- rest,而不是next(重启,不是迭代)//Using next causes a lazy seq to be one element less lazy(因为需要检查‘下一个’)
- 为产生rest,仅仅保存必须的状态(相当于保存了一个函数对象?),避免过早的realize
- ??(let [r (range 1e9)] [(first r) (last r)]) ;=> [0 999999999]
- map,reduce,filter
- delay,force宏:不要直接使用,容易出错。。。?
- (if-let [res :truthy-thing] (println res)) ;另外的when-let
- *A lazy, tail-recursive quicksort implementation //看起来可以用它来求max/min-N?
- (def fifth (comp first rest rest rest rest))
- (defn fnth [n]
- (apply comp
- (cons first
- (take (dec n) (repeat rest)))))
- (cons first
- (apply comp
- partial isn't currying. ?
- Because Clojure allows functions of variable number of arguments, currying makes little sense
- Higher-order functions:以函数为参数、返回值
- pure functions:相同输入总是得到相同输出;没有副作用
- ... as constant, or referentially transparent:例,(defn manip-map [f ks m] (conj m (keys-apply f ks m)))
- keys-apply: (defn keys-apply [f ks m] (let [only (select-keys m ks)] (zipmap (keys only) (map f (vals only)))))
- ... as constant, or referentially transparent:例,(defn manip-map [f ks m] (conj m (keys-apply f ks m)))
- 命名参数:(defn slope [& {:keys [p1 p2] :or {p1 [0 0] p2 [1 1]}}] ... ;(slope :p1 [4 15] :p2 [3 21])
- :pre :post
- closure
- polymorphism
- compile-time VS run-time
- In current versions of Clojure, each function definition gets its own class.
- mundane recursion(显示的递归调用)
- 计数器+recur
- lazy-seq
- Scheme(Lambda Papers, Guy L. Steele and Gerald Sussman):actors被移除,仅保留functions => Generalized tail-call optimization
- 任何A到B的尾调用将释放A下的locals ...
- JVM不提供lco机制,recur也只能处理自己调自己的特殊情形
- 但至少Clojure为什么不在编译器层次把自己调自己的mundane recursion解析处理成recur呢?
- Tail position:
-
- fn, defn:(fn [args] expressions tail)
- loop:(loop [bindings] expressions tail)
- *...act as anonymous recursion points. Why recur indeed. (靠!)
-
- mutually recursive:trampoline??
- Continuation-passing style(CPS):1)必须保证bounded execution path;2)isn’t conducive to parallelization
- An accept function that decides when a computation should terminate
- A return continuation that’s used to wrap the return values
- A continuation function used to provide the next step in the computation
- e.g. A-* path-finding
large-scale design
Macros
- (-> 25 Math/sqrt int list):展开为(list (int (Math/sqrt 25)))
- (eval '(list 1 2))
- defmacro
- (defmacro resolution [] `x) => (macroexpand '(resolution)) ;Common LISP中需避免名字捕捉
- domain DSL structure ~ XML/JSON(都是树形结构)
- Anaphora(上下文的代词引用)
- Scala:Array(1, 2, 3, 4, 5).map(2 * _)
- Anaphora don’t nest, and as a result are generally not employed in Clojure. ?
- => if-let when-let ~'symbol
- selective name capturing
- proxy ~'this
- A hygienic macro is one that doesn’t cause name capturing at macro expansion time.(FUCK,学术气味太浓了吧?)
- 管理资源(try/catch/finally):with-open
- (defmacro with-resource [binding close-fn & body]
- `(let ~binding
- (try
- (do ~@body)
- (finally
- (~close-fn ~(binding 0))))))
- (try
- `(let ~binding
- (defmacro with-resource [binding close-fn & body]
- macros returning functions
- contract
Combining data and code
- ns:java.lang、clojure.core自动导入
- in-ns:不自动导入clojure.core
- create-ns
- ns-map
- Var mapping::exclude, :only, :as, :refer-clojure, :import, :use, :load, :require.
- Yegge’s UDP:beget, get, put, has?, forget
- Multimethods
- defmulti defmethod
- derive(继承关系?)
- prefer-method 解决多继承情况下的冲突?
- juxt
- Records
- (defrecord TreeNode [val l r])
- Protocols
- In fact, the first parameter to a protocol function corresponds to the target object ...
- Clojure-style mixins
Java.next
- proxy
- construct-proxy、get-proxy-class、init-proxy、update-proxy、
- gen-class
- (doto (StringBuilder. "abc") (.append (char-array [\x \y \z])))
- into-array ;type based on the first element of the sequence
- ... Instead, variadic methods expect an array as their final argument
- All Clojure functions implement...
- java.util.Comparator
- java.lang.Runnable
- java.util.concurrent.Callable
- Java里调用Clojure数据结构
- Clojure sequential collections conform to the immutable parts of the java.util.List
- java.util.List
- java.lang.Comparable
- java.util.RandomAccess
- java.util.Collection
- java.util.Set
- definterface
- (definterface ISliceable (slice [^int s ^int e]) (^int sliceCount []))
- (def dumb (reify user.ISliceable (slice [_ s e] [:empty]) (sliceCount [_] 42)))
- (definterface ISliceable (slice [^int s ^int e]) (^int sliceCount []))
- defprotocol ?
- extend
- extend-type
- 为了调用checked Exception的Java方法,Clojure中的所有函数都throws Exception ... ?
- Runtime versus compile-time exceptions
- (defn explode [] (explode)) ==> (try (explode) (catch Exception e "Stack is blown"))
- In Java, catching exceptions at the level of Throwable is considered bad form
- The way to throw a compile-time exception is to make sure your throw doesn’t occur within a syntax-quoted form
- (defmacro -?> [& forms]
- `(try (-> ~@forms)
- (catch NullPointerException _# nil)))
- `(try (-> ~@forms)
- ... then bear in mind that it’s rare for Clojure core functions to throw exceptions
Mutation
- A tangled web of mutation means that any change to your code potentially occurs in the large.
- 4 major mutable references: Refs, Agents, Atoms, and Vars.
- parallelism support: futures, promises, and a trio of functions pmap, pvalues, and pcalls
- STM with MVCC & snapshot isolation
- Time, State, Identity
- Clojure has but one transaction per thread, ...
- when a restart occurs in the (conceptual) subtransaction clojure.b, it causes a restart of the larger transaction.
- orphaned locks (locks held by a thread that has died)
- Any I/O operation in the body of a transaction is highly discouraged. ?事务不带外部磁盘IO怎么做?logging?
- (io! (.println System/out "Haikeeba!")) ;警告
- get in and get out as quickly as possible.
- When to use Refs
- ideal use:Coordinated、Retriable
- 其他:Agent:Asynchronous;Atom:Retriable;Var:Thread-local。
- Coordinated: reads and writes to multiple refs can be made in a way that guarantees no race conditions
- Value access via the @ reader feature or the deref function
- 可绑定set-validator
- [脚注] Except for ref-set on Refs, reset! on Atoms, and set! on Vars. ???
- alter
- (stress-ref (ref 0 :min-history 15 :max-history 30))
- ideal use:Coordinated、Retriable
- When to use Agents
- Each Agent has a queue to hold actions that need to be performed on its value
- 串行IO?(def log-agent (agent 0)) ...
- the current state of an Agent can be observed cheaply
- send
- send-off
- 错误处理::continue and :fail
- (restart-agent log-agent 2500 :clear-actions true)
- When to use Atoms
- Once an Atom’s value is set, it’s set, and it doesn’t roll back when a transaction is retried
- ... holding a function’s memoization cache is idempotent on update
- memoize
- When to use locks
- When to use futures(`one way to perform parallel computation`)
- (time (let [x (future (do (Thread/sleep 5000) (+ 41 1)))] [@x @x]))
- When to use promises
- placeholders for values whose construction is fulfilled by another thread via the
deliver function - Promises are write-once; any further attempt to deliver will throw an exception
- *A macro for transforming a callback-based function to a blocking call
- ?Deterministic deadlocks
- placeholders for values whose construction is fulfilled by another thread via the
- Parallelism
- as-futures与with-promises
- pvalues:it returns a lazy sequence of the results of all the enclosed expressions,例如:(pvalues 1 2 (+ 1 2))
- pmap
- pcalls
- Vars and dynamic binding
- binding宏
- def defn defmacro defonce defmulti defdynamic*
- (resolve 'x)、(bound? #'x)、(thread-bound? #'x)
- *with-local-vars、deref、var-get
- Dynamic scope(全局变量?)
- (with-precision 4
- (doall (map (fn [x] (/ x 3)) (range 1M 4M)))) #强制map解析
- bound-fn ?
Tangential considerations
- Performance:
- Type hints
- Transients
- Chunked sequences
- Memoization
- Understanding coercion