前言
每逢學習一個新的語言時總要先了解這門語言支持的數據類型,因為數據類型決定這門語言所針對的問題域,像Bash那樣內置只支持字符串的腳步明顯就是用于文本處理啦。而數據類型又分為標量類型(Scalar)、結構類型(Struct)和集合類型(Collection),標題中的簡單類型實質就是指標量類型。
cljs中內置的標量類型比js的豐富得多,一方面方便了操作,另一個方面增加了學習成本,因此從js轉向cljs時可能會略感不適,下面我們一起來認識吧!
標量類型一覽
;; 空值/空集
nil
;; 字符串,必須使用雙引號包裹
"I am a string!"
;; 字符,以斜桿開頭
\&
\newline
;; 布爾類型(Boolean),nil隱式類型轉換為false,0和空字符串等均隱式類型轉換為true
true
false
;; 長整型(Long)
1
;; 浮點型(Float)
1.2
;; 整型十六進制
0x0000ff
;; 指數表示法
1.2e3
;; 鍵(Keyword),以:為首字符,一般用于Map作為key
:i-am-a-key
;; Symbol,標識符
i-am-symbol
;; Var
i-am-var
;; Special Form
;; 如if, let, do等
(if pred then else?)
(let [a 1] expr1 expr2)
(do expr*)
;; 函數
(fn [a]
(println a))
;; 宏
(defmacro out [s]
`(println ~s))
Keyword真心不簡單啊!
位于cljs.core/Keyword
的關鍵字并不是僅僅如上述那樣簡單,其實一共有3種定義方式:
1.所見即所得
;; 通過literal來定義
:i-am-a-keyword
:i-am-a-namespace/i-am-a-keyword
;; 通過keyword函數來定義
(keyword "i-am-a-keyword")
(keyword "i-am-a-namespace" "i-am-a-keyword")
2.自動擴展為以當前命名空間為前綴
(ns cljs.user)
;; 自動擴展為以當前命名空間為前綴的keywork
::keyword ;;=> :cljs.user/keyword
3.自動擴展為
;; 自動查找以aliased-ns為別名的命名空間,并以找到的命名空間作為前綴創建keyword
;; 因此需要先通過require 引入命名空間才能通過別名解析出原來的命名空間
(ns cljs.user
(:require '[test.core :as test]))
::test/keyword ;;=> :test.core/my-keyword
另外Keyword還可以作為函數使用呢!
(def person {:name "fsjohnhuang", "sex" "male"})
(:name person) ;;=> "fsjohnhuang"
("sex" person) ;;=> 報錯
(get person "sex") ;;=> "male"
什么是Symbol?
在任何Lisp方言中Symbol作為標識符(Identity),如命名空間名稱、函數名稱、變量名稱、Special Form名稱等等。而凡是標識符均會被限制可使用的字符集范圍,那么合法的cljs.core/Symbol
需遵守以下規則:
- 首字符不能是
[0-9:]
- 后續字符可為
[a-zA-Z0-9*+-_!?|:=<>$&]
- 末尾字符不能是
:
- 區分大小寫
命名習慣:
- 全小寫
- 單詞間以
-
分隔 - 常量和全局標識,首尾為
*
,如*main-cli-fn*
*x
,標識內置變量,且經常值變化x?
,標識斷言函數x!
,標識產生副作用的函數x-
,標識其將產生私有方法,如defn-
和deftest-
_
,標識可忽略的symbol
既然Symbol僅僅作為標識符來使用,為何不見JS、C#等會將標識符獨立出來作為一種類型呢?原因十分簡單但又難以理解——Lisp中代碼即數據,數據即代碼。作為Lisp的方言cljs自然傳承了這一耀眼的特性!
;; 定義一個List實例,其元素為a和b兩個Symbol實例
(def symbol-list (list 'a 'b))
大家有沒有注意到'
這個符號啊?由于symbol根據它在列表中的位置解析為Special Form或Var,為阻止這一過程需要通過quote
函數來處理,而'
就是quote
的reader macro。不信大家試試(cljs.reader/read-string "'a")
它會擴展為(cljs.core/quote a)
另外
;; 判斷是否為cljs.core/Symbol類型
(symbol? 'a) ;;=> true
;; symbol可以作為函數使用
(def a {'b 1})
('b a) ;;=> 1
Var又是什么呢?
在clj/cljs中Var是一個容器,其內容為指向實際值的地址,當其內容為nil時稱之為unbound,非nil時則稱為bound。而一個Var可以對應1~N個Symbol。
;; Symbol a和b都對應同一個Var,這個Var指向1所在的內存地址
(def a 1)
(def b 1)
這個和JAVA、C#中的String是一樣的。另外Clojure還有一個十分有趣的特性就是Symbol直接綁定值,中間沒有Var,因此就不存在重新賦值的可能
(defn say [s]
(println s))
(defn say1 [s]
(def s 2)
(println s))
(say "say") ;;=> say
(say1 "say1") ;;=> say1
和Symbol同樣,Var可以作為數據處理,不過由于Var會根據其所在列表中的位置解析為是Macro還是函數還是值,因此需要通過#'
來阻止,而#'
就是var
的reader macro。
(def b 1)
(def c 2)
(def a (list #'b #'c))
注意:#'
或var
操作前必須要先定義好同名變量、內置或第三方庫已定義的變量,否則會報錯。
Special Form又是什么鬼?
實質上就是語言原語,其他函數和Macro均基于它們來構造,當解析器遇到一個Symbol時會解析的順序是Special Form
-> Var
。
如if
就是一個原語,即使是Macro也沒有辦法從無來構造一個,不信大家自己試試吧!
部分常用的Special Form如下:
(def symbol init?)
(if test then else?)
(do exprs*)
(let [binding*] exprs*)
(quote form)
(var symbol)
(fn name? [params*]
exprs*)
(fn name?
([params*]
exprs*)+)
(fn name? [params*]
condition-map? exprs*)
(fn name?
([params*]
condition-map?
exprs*)+)
(loop [binding*]
exprs*)
(recur exprs*)
(throw expr)
(try expr* catch-clause* finally-clause?)
怎么函數也納入標量呢?
函數式編程當中第一條規則就是“函數是一等公民”,就是函數和String、Integer等一樣可以作入參、函數返回值,更確切來說函數的構造不依賴其他類型或類型實例。而面向對象中,沒有函數只有方法,而方法的構造前必須先構建其所依賴的類型或類型實例。
另外cljs中確實是用定義變量的方式來定義函數
(defn a [x] (println x))
;; defn是macro,實質上會展開成
(def a (fn [x] (println x)))
是不是清楚多了啊!
總結
本文較詳盡地介紹了Keyword,然后稍微介紹了Symbol、Var和Special Form,而Lisp中“代碼即數據,數據即代碼”需要結合Symbol的解釋過程說明效果才有所體現,這個由于篇幅較大,就打算日后再另起一篇來描述了。
作為函數式編程語言,cljs的函數定義又怎么會只有(defn name [params*] exprs*)
呢?下一篇(cljs/run-at (JSVM. :all) "細說函數"),我們一起細說吧!
尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/7119333.html ^_^肥仔John
REF
文章列表