Programming Clojure笔记之二——探索Clojure

来源:互联网 发布:零售超市收银软件 编辑:程序博客网 时间:2024/04/30 20:33

Forms

Clojure具有同像性(homoiconic),即代码与其数据的结构一致。Clojure代码主要由以下形式(Forms)构成。这些形式可以理解为其他程序设计语言中的数据类型。

Form Example(s) Boolean true, false Character \a Keyword :tag, :doc List (1 2 3), (println “hello”) Map {:name “Bill”, :age 42} Nil nil Number 1, 4.2 Set #{:snap :crackle :pop} String “hello” Symbol user/foo, java.lang.String Vector [1 2 3]

数值(Number)

数值的字面量是一种Form,对其求值得到本身。

42-> 42;vector is a form[1 2 3]-> [1 2 3];list is a form,此处解析为函数调用(+ 1 2)-> 3(+)-> 0(concat [1 2] [3 4])-> [1 2 3 4](> 5 2)-> true;整数相除得到一个Clojure类型Ratio(/ 22 7)-> 22/7;如果需要实际的除法运算,使用浮点数字面量(/ 22.0 7)-> 3.142857142857143;或者这样得到整数相除的商和余数(quot 22 7)-> 3(rem 22 7)-> 1;任意精度的浮点数运算在数字后加上M,结果是一个BigDecimal(+ 1 (/ 0.00001 1000000000000000000))-> 1.0(+ 1 (/ 0.00001M 1000000000000000000))-> 1.00000000000000000000001M;任意精度的整数运算,则在其中一个数字后加上N即可,结果是一个BigInt(* 1000N 1000 1000 1000 1000 1000 1000)-> 1000000000000000000000N

符号(Symbol)

符号用来给Clojure中的事物命名,如:
- 类似str和concat的函数。
- 类型+和-的操作符,本质上也是函数。
- Java类,如java.lang.String
- 名字空间如clojure.core和Java包如java.lang
- 数据结构和引用

String和Character

String也是一种Form。Clojure中的String就是Java中的String。

(.toUpperCase "hello")-> "HELLO"

函数前的点表明这是调用的Java方法而不是Clojure函数。Clojure没有封装所有的函数,但是toString得到了封装。这就是str函数。(str & args)
该函数将多个参数并在一起,并且可以略过nil。

(str 1 2 nil 3)-> "123"

Clojure中的字符也是Java字符。字面量表达方式为\{letter}

(str \h \e \y \space \y \o \u)-> "hey you"

Clojure也没有封装Java的字符函数。只能直接使用Java方法。

(Character/toUpperCase \s)-> \S

字符串就是字符序列。在字符串上调用序列函数就得到了一个字符序列。例如交插两个字符串:

(interleave "Attack at midnight" "The purple elephant chortled")-> (\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);如果要将结果转为字符串,不能简单的使用str(str (interleave "Attack at midnight" "The purple elephant chortled"))-> "clojure.lang.LazySeq@d4ea9f36";应该使用apply函数(apply str (interleave "Attack at midnight" "The purple elephant chortled"))-> "ATthtea cpku raptl em iedlneipghhatn";最后,将交插的结果还原(apply str (take-nth 2 "ATthtea cpku raptl em iedlneipghhatn"))-> "Attack at midnight"

Booleans和nil

  • true 的值为true,false的值为false
  • nil在布尔上下文中被解析为false
  • 除了false和nil,其他所有值在布尔上下文中解析为true
    谓词函数(一般以?结尾的函数)用于判定表达式的真实值。
(true? true)-> true(true? "foo")-> false(zero? 0.0)-> true(zero? (/ 22 7))-> false

Maps、Keywords和Records

;定义一个Map(def inventors {"Lisp" "McCarthy" "Clojure" "Hickey"})-> #'user/inventors;或者在键值对之间加上逗号(Clojure将之视为空格)(def inventors {"Lisp" "McCarthy", "Clojure" "Hickey"})-> #'user/inventors;Map是函数,传入键,得到相应的值(inventors "Lisp")-> "McCarthy"(inventors "Foo")-> nil;或者使用get函数,可以传入一个缺省值(get inventors "Lisp" "I dunno!")-> "McCarthy"(get inventors "Foo" "I dunno!")-> "I dunno!";Keyword类似于symbol,不过以冒号开头。并且总是解析为自身,symbol则指向其他值。使用Keyword作为Map的键(def inventors {:Lisp "McCarthy" :Clojure "Hickey"})-> #'user/inventors;使用keyword获取map中关联的值(inventors :Clojure)-> "Hickey";keyword也是函数,因而这样也可以获取map中的值。(:Clojure inventors)-> "Hickey";如果很多map有相同的键,这时可以定义一个record(defrecord Book [title author])-> user.Book;实例化一个记录并且绑定到变量b(def b (->Book "Anathem" "Neal Stephenson"));另一种实例化record的方法(Book. "Anathem" "Neal Stephenson")-> #user.Book{:title "Anathem", :author "Neal Stephenson"}  ;从变量b中获取值(:title b)-> "Anathem"

Reader macros

Clojure中的forms是由reader读取并转化为数据结构的。reader被一些特殊的字符触发后的特殊行为称之为reader macro。最典型的例子就是注释,这时macro字符就是分号,触发的特殊行为就是,忽略直到行尾的所有内容。
reader macros一般是冗长list forms的省略形式。阻止求值的单引号就是其中之一。

'(1 2)-> (1 2);'(1 2) is equivalent to the longer (quote (1 2)):(quote (1 2))-> (1 2)

下表列出了其他的reader macro。

Reader Macro Example(s) 匿名函数(Anonymous function) #(.toUpperCase %) 注释(Comment) ;sigle-line comment 解引用(Deref) @form=>(deref form) 元数据(Metadata) ^metadata form 引用(Quote) ‘form=>(quote form) 正则表达式模式(Regex pattern) #"foo"=>a Syntax-quote `x Unquote ~ Unquote-splicing ~@ Var-quote #'x=>(var x)

函数

使用defn定义函数如下:

;基本形式(defn name doc-string? attr-map? [params*] body)(defn greeting"Returns a greeting of the form 'Hello, username.'"[username](str "Hello, " username));调用函数(greeting "world")-> "Hello, world";查看函数的文档(doc greeting);不提供参数调用greeting会产生异常。事实上,函数可以有多个参数列表和相应的函数体;以下是函数定义的完整形式(defn name doc-string? attr-map? ([params*] body)+)(defn greeting"Returns a greeting of the form 'Hello, username.'Default username is 'world'."([] (greeting "world"))([username] (str "Hello, " username)));函数的不定参数(defn date [person-1 person-2 & chaperones](println person-1 "and" person-2 "went out with" (count chaperones) "chaperones."))

匿名函数

;不使用匿名函数(defn indexable-word?     [word] (> (count word) 2))(require '[clojure.string :as str])(filter indexable-word? (str/split "A fine day it is" #"\W+"))-> ("fine" "day");匿名函数定义方式(fn [params*] body);使用匿名函数(filter (fn [w] (> (count w) 2)) (str/split "A fine day" #"\W+"))-> ("fine" "day");使用隐式参数(%1,%2...)使得匿名函数更加简洁;#(body)(filter #(> (count %) 2) (str/split "A fine day it is" #"\W+"))-> ("fine" "day");在函数内部将匿名函数绑定到symbol(defn indexable-words [text]    (let [indexable-word? (fn [w] (> (count w) 2))]        (filter indexable-word? (str/split text #"\W+"))));匿名函数作为返回值(defn make-greeter [greeting-prefix]    (fn [username] (str greeting-prefix ", " username)))

变量(var)、绑定(Binding)和命名空间(Namespace)

当使用def或者defn定义了一个对象时,该对象就存储在一个var中。

(def foo 10)-> #'user/foo;符号user/foo指向一个变量,变量绑定了值10。你可以直接引用一个var(var foo)-> #'user/foo;引用一个var,更常见的是使用其reader macro形式(#')#'foo-> #'user/foo;一般情况下不需要直接引用var,可以忽略symbol和var的区别。;需要注意的是,var不仅仅只能存储值。还可以包含元数据等等。

绑定

var绑定到了名字,其他绑定还包括实参值绑定到了函数形参名。函数的参数绑定具有词法作用域(lexical scope),仅仅在函数体内可见。然而函数不是进行词法绑定的唯一方式。let这种特殊的form是专门用来进行词法绑定的。

(let [bindings*] exprs*);绑定在exprs有效,exprs中最后一个表达式的值就是let的值。(defn square-corners [bottom left size]    (let [top (+ bottom size) right (+ left size)] [[bottom left] [top left] [top right] [bottom right]]))

解构(Destructuring)

有时函数只需要容器数据结构的一部分数据,而不想将参数绑定到整个容器。Clojure中使用destructuring来解决这个问题

;使用解构前(defn greet-author-1 [author]    (println "Hello," (:first-name author)))(greet-author-1 {:last-name "Vinge" :first-name "Vernor"})| Hello, Vernor;使用解构后(defn greet-author-2 [{fname :first-name}]    (println "Hello," fname))(greet-author-2 {:last-name "Vinge" :first-name "Vernor"})| Hello, Vernor;前面使用map来解构关联容器,那么vector可以用来解构顺序容器。(let [[x y] [1 2 3]][x y])-> [1 2];依照惯例使用下划线绑定不关心的值(let [[_ _ z] [1 2 3]]z)-> 3;使用:as来同时绑定全部数据(let [[x y :as coords] [1 2 3 4 5 6]]  (str "x: " x ", y: " y ", total dimensions " (count coords)))-> "x: 1, y: 2, total dimensions 6";综合示例(require '[clojure.string :as str])(defn ellipsize [words]  (let [[w1 w2 w3] (str/split words #"\s+")]    (str/join " " [w1 w2 w3 "..."])))(ellipsize "The quick brown fox jumps over the lazy dog.")-> "The quick brown ..."

命名空间

;确认当前命名空间存在一个符号,不存在则返回nil(resolve 'foo)-> #'user/foo;切换命名空间,不存在则创建user=> (in-ns 'myapp) -> #<Namespace myapp> myapp=>;切换到新的命名空间后java.lang包会自动导入,然而需要手动导入clojure.coremyapp=> (clojure.core/use 'clojure.core)-> nil;导入其他Java包(import '(java.io InputStream File))-> java.io.File(.exists (File. "/tmp"))-> true;声明当前文件的命名空间并导入clojure和Java包(ns examples.exploring  (:require [clojure.string :as str])  (:import (java.io File)))

调用Java

;创建对象(def rnd (new java.util.Random))-> #'user/rnd;调用方法(. rnd nextInt 10)-> 8;静态方法(. Math PI)-> 3.141592653589793;javadoc函数(javadoc java.net.URL)

控制流

if分支

(defn is-small? [number]  (if (< number 100) "yes" "no"))

使用do引入副作用

;do接受任意数量的form并且依次求值,返回最后的form值(defn is-small? [number]   (if (< number 100)    "yes"   (do     (println "Saw a big number" number)      "no")))

使用loop/recur进行迭代

(loop [result [] x 5]   (if (zero? x)    result    (recur (conj result x) (dec x))))-> [5 4 3 2 1];一般不用loop/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]

for

;Clojure中的for用来sequence comprehension(defn index-filter [pred coll]   (when pred    (for [[idx elt] (indexed coll) :when (pred elt)] idx)))(index-filter #{\a \b} "abcdbbb")-> (0 1 4 5 6)(index-filter #{\a \b} "xyz")-> ()

元数据

;str变量的元数据(meta #'str)-> {:ns #<Namespace clojure.core>,    :name str,    :file "core.clj",    :line 313,    :arglists ([] [x] [x & ys]),    :tag java.lang.String,    :doc "With no args, ... etc."};使用元数据reader macro添加元数据,元数据由编译器添加到var中^metadata form;定义了shout函数,并添加了:tag元数据。表明函数接受String类型返回String类型。(defn ^{:tag String} shout [^{:tag String} s] (.toUpperCase s))-> #'user/shout;由于:tag键很常用,因此简写形式如下(defn ^String shout [^String s] (.toUpperCase s))-> #'user/shout;如果觉得元数据影响函数阅读,可以放到最后(defn shout  ([s] (.toUpperCase s))  {:tag String})
0 0