Programming Clojure笔记之二——探索Clojure

Forms

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

FormExample(s)
Booleantrue, false
Character\a
Keyword:tag, :doc
List(1 2 3), (println “hello”)
Map{:name “Bill”, :age 42}
Nilnil
Number1, 4.2
Set#{:snap :crackle :pop}
String“hello”
Symboluser/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 MacroExample(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.core
myapp=> (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})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值