http://clojuredocs.org/, 在線Clojure語法例子
Installing Clojure
Clojure is an open-source project hosted at github.com.
git clone https://github.com/clojure/clojure.git
This will download the code from the master branch into the clojure directory in your workspace.
Clojure is a Java project, and it uses the Ant build system.
ant
Running this command will leave you with an appropriate Clojure JAR file.
Open REPL,
java –jar /path/to/clojure.jar
The Clojure REPL
REPL, 命令行工具
user=> (defn hello [name] (str "Hello, " name)) user=> (hello "Stu") Hello, Stu user=> (hello "Clojure") Hello, Clojure (str *1 " and " *2) "Hello, Clojure and Hello, Stu"
doc and find-doc, 幫助文檔
The doc, look up the documentation associated with any other function or macro.
user=> (doc +) ------------------------- clojure.core/+ ([] [x] [x y] [x y & more]) Returns the sum of nums. (+) returns 0.
The find-doc function accepts a string, which can be a regex pattern. It then finds the documentation for all functions or macros whose names or associated documentation match the supplied pattern.
user> (find-doc "lazy") ------------------------- clojure.core/concat ([] [x] [x y] [x y & zs]) Returns a lazy seq representing the concatenation of... ------------------------- clojure.core/cycle ([coll]) Returns a lazy (infinite!) sequence of repetitions of... ... more results
基本語言特征
Prefix Notation
Clojure code uses prefix notation (also called polish notation) to represent function calls.
其實很多人會對於這個很不習慣, 主要是因為數學計算操作符, 比如(+ 1 2)
其實對於函數, prefix是一種常態, 換個寫法 add(1, 2), 是不是就比較容易接受了
所以奇怪的不是prefix, 而是其他的語言, 為了迎合大家的使用習慣對數學操作符做了特殊的處理, 這個導致了復雜的語法.
而對於clojure, 沒有特例, 一切都是function的語法, 也可以說no syntax
這樣最大的好處, 就是非常便於generate and manipulate code
Case Sensitive
Most Lisps are not case sensitive. Clojure, on the other hand, is case sensitive.
Comments, 注釋
單行: ; ;; ;;; Lisper習慣於用越多;表示越重要或者越概要的注釋
; 單行注釋
;; 函數注釋
;;; macro或者defmulti的注釋
;;;; ns注釋
多行:
(comment "
...1...
...2...
")
Exception, 異常
user=> (/ 1 0) java.lang.ArithmeticException: Divide by zero (NO_SOURCE_FILE:0)
查看detailed stack trace
The *e special variable holds the last exception. Because Clojure exceptions are Java exceptions, you can call Java methods such as printStackTrace( ):
user=> (.printStackTrace *e) java.lang.ArithmeticException: Divide by zero (NO_SOURCE_FILE:0) at clojure.lang.Compiler.eval(Compiler.java:4094) at clojure.lang.Repl.main(Repl.java:87) Caused by: java.lang.ArithmeticException: Divide by zero at clojure.lang.Numbers.divide(Numbers.java:142) at user.eval__2677.invoke(Unknown Source) at clojure.lang.Compiler.eval(Compiler.java:4083) ... 1 more
Symbols, Vars, Bindings
Symbols, 名稱, 標識符
Broadly stated, a symbol is an identifier that resolves to a value.
Symbols在clojure里面可以表示, Var name, function name, operators name, macro name……
命名規則
Symbol names are case sensitive, and user-defined symbols have the following restrictions:
• May contain any alphanumeric character, and the characters *, +, !, -, _, and ?.
• May not start with a number.
• May contain the colon character :, but not at the beginning or end of the symbol name, and may not repeat.
According to these rules, examples of legal symbol names include symbol-name, symbol_name, symbol123, *symbol*, symbol! , symbol? , and name+symbol. Examples of illegal symbol names would be 123symbol, :symbol: , symbol//name, etc.
區分大小寫, 通常都是小寫, 並以-分隔. 通常常量或全局, 首尾加*
By convention, symbol names in Clojure are usually lower-case, with words separated by the dash character (-).
If a symbol is a constant or global program setting, it often begins and ends with the star character (*). For example, a program might define (def *pi* 3.14159).
Symbol Resolution
resolve順序如下
special form –> local binding(let) --> thread dynamic binding (binding) -->root binding(def)
Vars, 變量
Vars can be defined and bound to symbols using the def special form.
Clojure中變量和其他語言不同就是, 不可變
通常通過def定義, 並bind到一個symbol(變量名稱)
所以反之, 從symbol可以reslove到var, 並evaluate出var-value.
user=> (def foo 10) ;定義var
#'user/foouser=> (resolve 'foo) ; resolve symbol (foo) to var
#'user/foouser=> user/foo ;evaluate var to value
user=> foo ;等於上面兩步, resolve和evaluate會自動完成
10
10
resolve(#’)只會取var本身, 而不會evaluate, 比如用於取var自身的metadata, (meta #'str)
Binding
Binding分為3種, root binding, local binding(lexical binding)和thread-local dynamic binding(Programming clojure – Concurrency)
Root binding
When you define an object with def or defn, that object is stored in a Clojure var. For example, the following def creates a var named user/foo:
(def foo 10) #'user/foo
You can refer to a var directly. The var(#') special form returns a var itself, not the var’s value:
(var a-symbol)
You can use var to return the var bound to user/foo:
(var foo) #'user/foo #'foo #'user/foo
通過def定義的var, 是一種root binding, 就是globle的, 各個線程都能看到
Root bindings also bind names to functions. When you call defn(封裝的def), it uses def internally. So function names like triple below are root bindings.
(defn triple [x] (* 3 x))
Local bindings
除了root binding以外, 還有一種binding叫local binding, 即lexical binding
最常見的local binding就是函數的參數
For example, in a function call, argument values bind to parameter names.
(defn triple [number] (* 3 number)) (triple 10) -> 30
A function’s parameter bindings have a lexical scope(只在詞法范圍內起作用所以叫lexical binding): they are visible only inside the text of the function body.
Functions are not the only way to have create a lexical binding. The special form let does nothing other than create a set of lexical bindings:
(let [bindings*] exprs*)
let非常有用, 底下給出了各種用法, 包括各種destructioin操作(集合中只有部分有用)
; 局部臨時變量定義: (let [x 10] (println x)) ; 定義多個變量, 並進行destruction: (let [[x y] [3 4]] (println (* x y))) ; 12 (let [x 3 y 4] (println (* x y))) (let [[x y] [3 4 5]] [x y]) ; [3 4] 多余的5被忽略 (let [[_ _ z] [3 4 5]] z) ; 5 (let [[a b & c] [1 2 3 4 5]] [a b c]) ; [1 2 (3 4 5)](let [a 10[x y] (split "2012-1" "-")b 20](str x "." y)) ; "2012.1"(let [{x 0 y 6} '[a b c d e f g]] [x y]) ; [a g] 0,6表示下標 ;多個變量之間可以依賴(后面的依賴前面的),這點*非常*非常*有用: (let [x 10 y (* x x) z (* 2 y)] (println z)) ; 200 ; let的執行體內可以調用多個函數: (let [x 10] (println x) (println (* x x)))
Namespaces and Libraries
Organizing Clojure Code
所有語言的命名空間都是用於代碼庫的組織, 否則放在一起太亂了
clojure一般都會在文件開頭加上一段命名空間和庫的聲明
Namespaces are the means by which you divide your Clojure code into logical groups, similar to packages in Java or modules in other languages.
Almost every Clojure source file begins with a namespace declaration using the ns macro.
The following code is an example of a namespace declaration:
(ns clojure.contrib.gen-html-docs
(:require [clojure.contrib.duck-streams :as duck-streams])
(:use (clojure.contrib seq-utils str-utils repl-utils def prxml))
(:import (java.lang Exception) (java.util.regex Pattern)))
切換namespace
You can switch namespaces with the ns, in-ns macro.
user=> (ns myapp)
user=> (in-ns ‘myapp)
When you create a new namespace, the java.lang package and the Clojure namespace are automatically available to you:
myapp=> String #=java.lang.String myapp=> #'doc #=(var clojure/doc)
其他的package, 你需要自己import !!
(import '(java.io File)) -> nil myapp=> (File/separator) -> "/"
加載namespace
Loading from a File or Stream
(load-file "path/to/file.clj")
(load-file "C:\\Documents\\file.clj")
對於stream, 需要使用load-reader
但其實這種方法使用的很少, 一般都會基於classpath, 否則會很麻煩
Loading from the Classpath
The Java Virtual Machine uses a special variable called the classpath, a list of directories from which to
load executable code. Clojure programs also use the classpath to search for source files.
clojure也會使用classpath來search source, 所以先要將工程所在目錄放到classpath里面
Clojure namespaces follow similar naming conventions to Java packages: they are organized hierarchically with parts separated by periods. A popular convention is to name your libraries using the reversed form of an Internet domain name that you control.
clojure采用和Java相同的ns命名規則, 比如, com.example.my-cool-library would be defined in the file com/example/my_cool_library.clj
Require, 等同python import
如題, 所以require后, 使用命名空間中的var, 必須每次加上namespace
(require 'introduction)
(take 10 introduction.fibs) -> (0 1 1 2 3 5 8 13 21 34)
(require 'com.example.lib) ;;一般形式 (require 'com.example.one 'com.example.two 'com.example.three) ;;可以添加多個 (require '[com.example.lib :as lib]) ;;別名 (require '(com.example one two three)) ;;前綴形式, 可以加入相同前綴的多個package (require '(clojure.java [io :as io2]) ;;在前綴形式中, 加別名 ; :reload, load all namespaces in the arguments ; :reload-all, beside :reload, need all dependent namespaces required by those namespaces. (require 'com.example.one 'com.example.two :reload) ; :verbose, prints debugging information user=> (require '(clojure zip [set :as s]) :verbose) (clojure.core/load "/clojure/zip") (clojure.core/load "/clojure/set") (clojure.core/in-ns 'user) (clojure.core/alias 's 'clojure.set)
Use, 等同Python from…import
其實clojure還有個命令叫refer, 可以把namespace里面的var都load進來, 避免每次都要加上namespace名, 但很少用
因為use = require + refer
用過python的都知道, 盡量不要使用from import *
同樣對於use, 也進來使用條件, only
(use 'clojure.core)
(use '[clojure.core :exclude (map set)])
(use '[clojure.core :rename {map core-map, set core-set}])
(use '[com.example.library :only (a b c)] :reload-all :verbose)
Import, importing Java classes
(import 'java.util.Date)
(import '(java.util.regex Pattern Matcher))
(import '(javax.swing Box$Filler)) ;javax.swing.Box.Filler
其他關於namespace
Namespace Metadata
Clojure does not specify any “official” metadata keys for namespaces
(ns #^{:doc "This is my great library." :author "Mr. Quux <quux@example.com>"} com.example.my-great-library)
Forward Declarations
Clojure也是var的定義必須放在var的使用之前, 如果出於代碼組織考慮一定要放后面, 先使用declare聲明
(declare is-even? is-odd?) (defn is-even? [n] (if (= n 2) true (is-odd? (dec n)))) (defn is-odd? [n] (if (= n 3) true (is-even? (dec n))))
Namespace-Qualified Symbols and Keywords
Symbols and keywords can be qualified with a namespace.
標識符和keywords都可以加上限定的命名空間, 並通過name和namespace來分別取得,
user=> (name 'com.example/thing) "thing" user=> (namespace 'com.example/thing) "com.example" user=> (name :com.example/mykey) "mykey" user=> (namespace :com.example/mykey) "com.example"
為了語法方便, 可以在keyword前面多加一個:來qualify到當前namespace
user=> (namespace ::keyword)
"user"
對於symbol, 使用backquote可以達到同樣效果
Although not explicitly for this purpose, the backquote ` reader macro can be used to create qualified symbols in the current namespace:
user=> `sym
user/sym
Public and Private Vars
By default, all definitions in a namespace are public, meaning they can be referenced from other namespaces and copied with refer or use. Sometimes need “internal” functions that should never be called from any other namespace.
兩種方法,
defn- macro
add :private metadata to the symbol you are defining
(def #^{:private true} *my-private-value* 123)
Querying Namespaces
The function all-ns takes no arguments and returns a sequence of all namespaces currently defined.
(keys (ns-publics 'clojure.core))
Forms, clojure語言的basic element, 合法的s-expression
Clojure is homoiconic, which is to say that Clojure code is composed of Clojure data.
When you run a Clojure program, a part of Clojure called the reader reads the text of the program in chunks called forms, and translates them into Clojure data structures.
Clojure then takes executes the forms.
Using Numeric Types
Numeric literals are forms. Numbers simply evaluate to themselves. If you enter a number, the REPL will give it back to you:
42 -> 42
A list of numbers is another kind of form. Create a list of the numbers 1,2, and 3:
'(1 2 3)
-> (1 2 3)
單引號的作用
Notice the quote in front of the list. This quote tells Clojure “do not evaluate what comes next, just return it.” The quote is necessary because lists are special in Clojure. When Clojure evaluates a list, it tries to interpret the first element of this list as a function (or macro) and the remainder of the list as arguments.
加,減,乘,比較
Many mathematical and comparison operators have the names and semantics that you would expect from other programming languages. Addition, subtraction, multiplication, comparison, and equality all work as you would expect:
(- 10 5) -> 5 (* 3 10 10) -> 300 (> 5 2) -> true (>= 5 5) -> true (< 5 2) -> false (= 5 2) -> false
除法
Division may surprise you, As you can see, Clojure has a built-in Ratio type. If you actually want decimal division, use a floating-point literal for the dividend:
(/ 22 7) -> 22/7 (/ 22.0 7) -> 3.142857142857143
If you want to stick to integers, you can get the integer quotient and remainder with quot( ) and rem( ):
(quot 22 7) ;整除 -> 3 (rem 22 7) ;余數 -> 1
Strings and Characters
Strings are another kind of reader form. Clojure strings are Java strings.
They are delimited by "(雙引號), and they can span multiple lines:
"This is a nmultiline string" -> "This is a nmultiline string"
直接調用java接口
Clojure does not wrap most of Java’s string functions. Instead, you can call them directly using Clojure’s Java interop forms:
(.toUpperCase "hello")
-> "HELLO"
The dot(句號) before toUpperCase tells Clojure to treat it as the name of a Java method instead of a Clojure function.
str
(str 1 2 nil 3) -> "123"
The example above demonstrates str’s advantages over toString( ). It smashes together multiple arguments, and it skips nil without error.
Clojure的字符和Java字符一樣, String就是字符序列, 所以clojure的序列function可以直接用於string
Clojure characters are Java characters.
Their literal syntax is \{letter}, where letter can be a letter, or newline, space, or tab.
Strings are sequences of characters. When you call Clojure sequence functions on a String, you get a sequence of characters back.
(interleave "Attack at midnight" "The purple elephant chortled") ;得到的是character list -> (\A \T \t \h \t \e \a \space \c \p \k \u \space \r \a \p \t \l \space \e \m \space \i \e \d \l \n \e \i \p \g \h \h \a \t \n)
(apply str (interleave "Attack at midnight" "The purple elephant chortled")) ;通過str轉化 -> "ATthtea cpku raptl em iedlneipghhatn"
Booleans and Nil, 比較嚴格,沒有python方便
Clojure’s rules for booleans are easy to understand:
• true is true and false is false.
• Only false, nil evaluates to false when used in a boolean context.
• Other than false and, nil, everything else evaluates to true in a boolean context.
注意在boolean context下, 除了false和nil以外, 全是true. 僅僅在boolean context下適用, 特別注意!!!
user=> (if '() "T" "F") "T" user=> (if 0 "T" "F") "T" user=> (if 1 "T" "F") "T" user=> (if nil "T" "F") "F" user=> (if false "T" "F") "F"
對於common Lisp, 空list為false, 但是在clojure中都是true, 特別注意!!!
(if '() "We are in Clojure!" "We are in Common Lisp!") -> "We are in Clojure!"
true?, false?, and nil?
Clojure includes a set of predicates for testing true?, false?, and nil?
這兒要小心的是, true?, 這個斷言, 只有在真正是true的時候才會返回true (因為不在boolean context)
(true? true) -> true (true? "foo") -> false
所以對於下面的filter, 你如果想當然會返回[11235], 錯, 只會范圍nil, 因為里面確實沒有true
(filter true? [1 1 2 false 3 nil 5]) -> nil (filter identity [1 1 2 false 3 nil 5]) ;這樣才work -> (1 1 2 3 5)
Maps, python中的字典
A Clojure map is a collection of key/value pairs. Maps have a literal form surrounded by curly braces.
You can use a map literal to create a lookup table for the inventors of programming languages:
(def inventors {"Lisp" "McCarthy" "Clojure" "Hickey"})
Maps are function
If you pass a key to a map, it will return that key’s value, or it will return nil if the key is not found:
(inventors "Lisp") "McCarthy" (inventors "Foo") nil
Get, handle missing
(get a-map key not-found-val?)
get allows you to specify a different return value for missing keys:
(get inventors "Lisp" "I dunno!") "McCarthy" (get inventors "Foo" "I dunno!") "I dunno!"
keyword, 常用於map的key, 是function
Because Clojure data structures are immutable and implement hash-Code correctly, any Clojure data structure can be a key in a map. That said, a very common key type is the Clojure keyword.
A keyword is like a symbol, except that keywords begin with a colon (:)
Keywords resolve to themselves:
:foo :foo
Keywords are also functions. They take a map argument and look themselves up in the map.
(inventors :Clojure) "Hickey" (:Clojure inventors) "Hickey"
這也是為什么使用keyword作為key的重要原因, 因為keyword本身是function, 所以取值非常方便
struct, 預定義map的keys
If several maps have keys in common, you can document (and enforce) this fact by creating a struct with defstruct:
(defstruct name & keys)
但是這個名字真的起的不好, 討好c程序員? 定義struct, 很confuse
(defstruct book :title :author) (def b (struct book "Anathem" "Neal Stephenson"))
b
{:title "Anathem", :author "Neal Stephenson"}
Reader Macros, 語法糖
這是一些特殊的語法宏(macros), 為了便於coding而創建的DSL, 而且大部分reader macros, 是有標准的函數形式的, 比如, ;和comment, ‘和quote
同時, 糖好吃也是要付代價的, 增加入門難度, 對於初學者大量的macros大大降低可讀性. 而且增加語法復雜度, 對於號稱no syntax的Lisp而言... 所以需要balance, 工具怎么樣用關鍵在人
Clojure forms are read by the reader, which converts text into Clojure data structures.
In addition to the basic forms, the Clojure reader also recognizes a set of reader macros. Reader macros are special reader behaviors triggered by prefix macro characters.
Many reader macros are abbreviations of longer list forms, and are used to reduce clutter. You have already seen one of these.
'(1 2) is equivalent to the longer (quote (1 2)):
Figure 2.2: Reader Macros
Functions, Clojure的核心概念
In Clojure, a function call is simply a list whose first element resolves to a function.
命名規范
Function names are typically hyphenated(-), as in clear-agent-errors.
If a function is a predicate, then by convention its name should end with a question mark. 約定俗成, 便於代碼理解所以加上?
user=> (string? "hello") true user=> (keyword? :hello) true user=> (symbol? :hello) true
函數定義
To define your own functions, use defn:
(defn name doc-string? attr-map? [params*] body) ;attr-map用於增加metadata
(defn greeting "Returns a greeting of the form 'Hello, name.'" [name] (str "Hello, " name))
(greeting "world") -> "Hello, world"
(doc greeting) ;查看doc string ------------------------- exploring/greeting ([name]) Returns a greeting of the form 'Hello, name.'
嚴格參數個數
Clojure functions enforce their arity, that is, their expected number of arguments.
If you call a function with an incorrect number of arguments, Clojure will throw an IllegalArgumentException.
(greeting) -> java.lang.IllegalArgumentException: \ Wrong number of args passed to: greeting (NO_SOURCE_FILE:0)
定義多組參數, 類似函數重載
(defn name doc-string? attr-map? ([params*] body)+ ) ;最后的+表明可以定義多組([params*] body)
(defn greeting "Returns a greeting of the form 'Hello, name.' Default name is 'world'." ([] (greeting "world" )) ([name] (str "Hello, " name)) ([greeting-prefix name] (str greeting-prefix " " name))) 這樣可以解決上面不給參數的問題, 也可以多參數...簡單的實現重載的概念
user=> (greeting) "Hello, world" user=> (greeting "hi" "df") "hi df"
可變參數, variable arity
You can create a function with variable arity by including an ampersand(&) in the parameter list. Clojure will bind the name after the ampersand to a list of all the remaining parameters.
(defn date [person-1 person-2 & chaperones] (println person-1 "and" person-2 "went out with" (count chaperones) "chaperones." ))
(date "Romeo" "Juliet" "Friar Lawrence" "Nurse") Romeo and Juliet went out with 2 chaperones.
Anonymous Functions
In addition to named functions with defn, you can also create anonymous functions with fn.
(fn [params*] body)
為什么需要匿名函數?
There are at least three reasons to create an anonymous function:
• The function is so brief and self-explanatory that giving it a name makes the code harder to read, not easier.
• The function is only being used from inside another function, and needs a local name, not a top-level binding.
• The function is created inside another function, for the purpose of closing over some data.
例子
我們用下面的代碼濾出長度大於2的word, 可以如下實現
(defn indexable-word? [word] (> (count word) 2))
(use 'clojure.contrib.str-utils) ; for re-split,breaks the sentence into words (filter indexable-word? (re-split #"\\W+" "A fine day it is" )) -> ("fine" "day" )
第一種用法, fn使表達更簡單
(filter (fn [w] (> (count w) 2)) (re-split #"\\W+" "A fine day"))
更簡潔的匿名函數表示方法,
There is an ever shorter syntax for anonymous functions, using implicit parameter names. The parameters are named %1, %2, etc., or just % if there is only one.
This syntax looks like: #body
(filter #(> (count %) 2) (re-split #"\\W+" "A fine day it is"))
第二種用法, 僅被用於某函數內部的函數, 並且需要Local name(理由是可能被調多次, 或使代碼簡化)
(defn indexable-words [text] (let [indexable-word? (fn [w] (> (count w) 2))] ;定義匿名函數, 並綁定給indexable-word? (filter indexable-word? (re-split #"\\W+" text))))
The combination of let and an anonymous function says to readers of your code: "The function indexable-word? is interesting enough to have a name, but is relevant only inside indexable-words."
第三種用法, 用於動態的創建function, 閉包
A third reason to use anonymous functions is when you dynamically creating a function at runtime.
(defn make-greeter [greeting-prefix] (fn [name] (str greeting-prefix ", " name))) (def hello-greeting (make-greeter "Hello")) -> #=(var user/hello-greeting) (def aloha-greeting (make-greeter "Aloha")) -> #=(var user/aloha-greeting) (hello-greeting "world") -> "Hello, world" (aloha-greeting "world") -> "Aloha, world"
Flow Control
Clojure has very few flow control forms. In this section you will meet if, do, and loop/recur. As it turns out, this is almost all you will ever need.
Branch with if, if-not
Clojure’s if evaluates its first argument. If the argument is logically true, it returns the result of evaluating its second argument:
(defn is-small? [number] (if (< number 100) "yes" "no" )) (is-small? 50) -> "yes" (is-small? 50000) -> "no"
對於if很容易理解, 唯一要注意的是, if不是一個標准的funciton, 而是一種special form
The rule for functions is “evaluate all the args, then apply the function to them.”
if does not follow this rule. If it did, it would always evaluate both the “in” and “else” forms, regardless of input.
In Lisp terminology if is called a special form because it has its own special-case rules for when its arguments get evaluated.
重要的是, 除了Clojure本身內嵌的special forms外, 我們可以用macros創造自己的special form.
這點極其總要, 他使得clojure的本身語言的schema是可以擴展的(java, c都不行, 你無法隨便加個關鍵字)
所以對於clojure, 它本身可以實現盡量少的語法和特殊form, 然后把后面的事交給程序員去做, 通過macro生成各種DSL
In addition to the special forms built into Clojure, you can write your own specialcase evaluation rules using macros.
Macros are extremely powerful, because they make the entire language programmable.
Clojure can afford to have a small set of flow control forms, because you can use use macros to add your own.
Cond (類似switch case)
cond is like the case statement of Clojure. The general form looks like the following:
(cond & clauses)
(defn range-info [x]
(cond
(< x 0) (println "Negative!")
(= x 0) (println "Zero!")
:default (println "Positive!")))
Introduce Side Effects with do
Clojure’s if allows only one form for each branch. What if you want to do more than one thing on a branch?
For example, you might want to log that a certain branch was chosen. do takes any number of forms, evaluates them all, and returns the last.
在if語法中, 每個條件分支中都只能執行一個form, 如果想執行多個form, 怎么辦? 用do
(defn is-small? [number] (if (< number 100) "yes" (do (println "Saw a big number" number) "no" ))) (is-small? 200) Saw a big number 200 -> "no"
首先, 在pure function中, do其實是沒用的, 因為他只會返回最后一個form的結果, 所以前面form的結果會直接被ignore, 所以寫了也白寫.
但在Clojure不是pure function, 引入了state和side effect
Printing a logging statement is an example of a side effect. The println does not contribute to the return value of is-small? at all.
Instead, it reaches out into the world outside the function and actually does something.
大部分語言不會強調side effect, 寫log, 寫DB, IO, 和一般的代碼混合在一起沒有區別, 但這樣造成了相當的復雜性.
而Clojure強調side effect的管理, 雖然不能避免, do就是一種顯示表示side effects的方法, 因為如果這些forms沒有side effect, 那么他們不會起任何作用, 所以使用do, 一定表明上面的form是有side effects的.
當然Clojure引入side effect, 只是不得已而為, 所以應該盡量少用do.
Many programming languages mix pure functions and side effects in completely ad hoc fashion.
Not Clojure. In Clojure, side effects are explicit and unusual. do is one way to say “side effects to follow.”
Since do ignores the return values of all its forms save the last, those forms must have side effects to be of any use at all.
Plan to use do rarely, and for side effects, not for flow control. For those occasions where you need more complex control flow than a simple if, you should define a recurrence with loop/recur.
When, When-not
和if的不同是, 沒有else子句,執行條件后的所有語句
例子:區別if和when,打印小於5的正整數
; 僅打印1 (loop [i 1] (if (< i 5) (println i) (recur (inc i)))) ;else ;正確,打印1~5 (loop [i 1] (if (< i 5) (do (println i) (recur (inc i))))) ;正確 when把條件判斷后的所有都執行 (loop [i 1] (when (< i 5) (println i) (recur (inc i))))
When-Not
when-not is the opposite of when, in that it evaluates its body if the test returns false (or nil). The general form looks similar to that of when:
(when-not test & body)
While
Clojure’s while macro works in a similar fashion to those seen in imperative languages such as Ruby and Java. The general form is as follows:(while test & body)
An example is
(while (request-on-queue?) (handle-request (pop-request-queue)))
Recur with loop/recur, 類似for
關於loop/recur參考這個blog, http://www.cnblogs.com/fxjwind/archive/2013/01/24/2875175.html
(defn countdown [result x] (if (zero? x) result (recur (conj result x) (dec x)))) ;(countdown (conj result x) (dec x)))) (defn demo-loop [] (loop [result [] x 5] (if (zero? x) result (recur (conj result x) (dec x)))) )
對於這種簡單的操作, Clojure’s sequence library, 直接可以實現, 無需直接使用recur….
(into [] (take 5 (iterate dec 5))) [5 4 3 2 1] (into [] (drop-last (reverse (range 6)))) [5 4 3 2 1] (vec (reverse (rest (range 6)))) [5 4 3 2 1]
Metadata
The Wikipedia entry on metadata begins by saying that metadata is “data about data.”
A very specific definition: metadata is a map of data attached to an object that does not affect the value of the object
Two objects with the same value and different metadata are considered equal (and have the same hash code).
However, metadata has the same immutable semantics as Clojure's other data structures;
modifying an object's metadata yields a new object, with the same value (and the same hash code) as the original object.
metadata本身很容易理解, 只要兩個object的value相同, 就算metadata不同, 仍然是equal的.
對於Clojure需要注意的是, metadata仍然有不變特性, 改變一個對象的metadata一樣會導致創建新的對象.
with-meta
You can add metadata to a collection or a symbol using the with-meta function:
(with-meta object metadata)
Create a simple data structure, then use with-meta to create another object with the same data but its own metadata:
(def stu {:name "Stu" :email "stu@thinkrelevance.com" }) (def serializable-stu (with-meta stu {:serializable true})) ;給map加上metadata, serializable:true, 注意元數據的改變需要定義新的變量serializable-stu
Metadata makes no difference for operations that depend on an object’s value, so stu and serializable-stu are equal:
(= stu serializable-stu) true ;因為value一樣, 所以是equal的
You can prove that stu and serializable-stu are different objects by calling identical?:
(identical? stu serializable-stu) false ;但是顯然他們不是同一個對象, 而是不同的對象
You can access metadata with the meta macro(reader macro ^), verifying that serializablestu has metadata and stu does not:
(meta stu) nil (meta serializable-stu) {:serializable true} ^stu
nil
^serializable-stu
{:serializable true}
Reader Metadata
The Clojure language itself uses metadata in several places.
在clojure中只要加上Reader, 意味着這是語言自帶的(reader或編譯器可識別), 對於metadata, 你可以任意給對象加上你自己的metadata, 但是其實clojure本身也預定義了一組metadata, 並且這些metadata是編譯器可識別的
如底下的例子, 對於var str(special form也屬於var), clojure會自動給它加上一組metadata
For example, vars have a metadata map containing documentation, type information, and source information. Here is the metadata for the str var:
(meta #'str) ;#' means var {:ns #<Namespace clojure.core>, :name str, :file "core.clj", :line 313, :arglists ([] [x] [x & ys]), :tag java.lang.String, :doc "With no args, ... etc."}
同時, 你可以use the metadata reader macro來修改這些metadata
#^metadata form
下面再給個例子, 定義函數的時候, 指定參數var的metadata, tag:string
編譯器就會知道並檢查參數, 當你傳入int, 會報錯
user=> (defn #^{:tag String} shout [#^{:tag String} s] (.toUpperCase s)) user=> (meta shout) {:ns #<Namespace user>, :name shout} user=> (meta #'shout) {:ns #<Namespace user>, :name shout, :file "NO_SOURCE_PATH", :line 38, :arglists ([s]), :tag java.lang.String} (shout 1) java.lang.ClassCastException: \ java.lang.Integer cannot be cast to java.lang.String
下面列出clojure支持的reader metadata
with-meta和reader metadata差別
這個#^和with-meta有什么區別?看下面的例子
metadata分兩種,
用戶自定義的metadata, 可以說是給value加上的metadata, 這個只有用戶理解, 用戶可以任意增加和修改. 需要使用with-meta.
clojure系統定義的metadata, 可以說是給var本身加上的metadata, 這個metadata是給編譯器看的, 用戶可以修改, 使用#^. 可以增加嗎?應該不行, 加也沒用, 系統不認
user=> (meta serializable-stu) ;value的metadata
{:serializable true}
user=> (meta #'serializable-stu) ;var的metadata
{:ns #<Namespace user>, :name serializable-stu, :file "NO_SOURCE_PATH", :line 2}
Type Hinting
Clojure是動態語言, 但是為了提高效率, 可以顯式定義type hint
如下面的例子, 定義參數text的type hint為^string
但是定義type hint本身不會限制類型, 可見下面例子, 雖然定義type hint為List, 但是真正的參數可以是anything類型
The ^ClassName syntax defines a type hint, an explicit indication to the Clojure compiler of the object type of an expression, var value, or a named binding.
(defn length-of [^String text] (.length text))
These hints are used by the compiler only to avoid emitting reflective interop calls
Type hints on function arguments or returns are not signature declarations: they do not affect the types that a function can accept or return.
(defn accepts-anything [^java.util.List x] x) ;= #'user/accepts-anything (accepts-anything (java.util.ArrayList.)) ;= #<ArrayList []> (accepts-anything 5) ;= 5 (accepts-anything false) ;= false