22[語法]Clojure - 基本語法



Clojure - 基本語法 - fxjwind - 博客園
http://www.cnblogs.com/fxjwind/archive/2013/01/22/2871860.html

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, Stuuser=> (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 zeroat 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/foo
user=> (resolve 'foo) ; resolve symbol (foo) to var#'user/foo
user=> user/foo ;evaluate var to value10
user=> foo ;等于上面兩步, resolve和evaluate會自動完成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

比如可以用于取var自身的metadata, (meta #'str) 通過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.Stringmyapp=> #'doc#=(var clojure/doc)

其他的package, 你需要自己import !!
(import '(java.io File))-> nilmyapp=> (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

其實對于clojure, 什么類型都可以作為keys, 但是最常用的是keyword, 它和一般symbol的區別就是以:開頭, 并且resolve的結果就是本身(一般var, resolve得到value), 所以Keywords比較適合用作keys Keywords are also functions. They take a map argument and look themselves up in the map.
(inventors :Clojure)"Hickey"(:Clojure inventors) "Hickey"

比較有意思, map和keyword本身都可以作為函數, 并且可以互相作為參數 這也是為什么使用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)

defstruct其實就是可以定義map的keys, 這樣在創建map的時候, 不需要重復寫key, 見下面例子 但是這個名字真的起的不好, 討好c程序員? 定義struct, 很confuse (defstruct book :title :author)(def b (struct book "Anathem" "Neal Stephenson"))
b{:title "Anathem", :author "Neal Stephenson"}

**Figure 2.1: Clojure Forms **

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")trueuser=> (keyword? :hello)trueuser=> (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)) [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}stunilserializable-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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容