前言
每逢學習一個新的語言時總要先了解這門語言支援的資料型別,因為資料型別決定這門語言所針對的問題域,像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