Clojure - 基本语法

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/foo

user=> (resolve 'foo) ; resolve symbol (foo) to var
#'user/foo

user=> user/foo ;evaluate var to value
10

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.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
其实对于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 
image

 

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

image

 

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

image

 

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

posted on 2013-01-22 18:03  fxjwind  阅读(6383)  评论(0编辑  收藏