(cljs/run-at (JSVM. :browser) "簡單型別可不簡單啊~")

^_^肥仔John發表於2017-07-05

前言

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

  1. 首字元不能是[0-9:]
  2. 後續字元可為[a-zA-Z0-9*+-_!?|:=<>$&]
  3. 末尾字元不能是:
  4. 區分大小寫

 命名習慣:

  1. 全小寫
  2. 單詞間以-分隔
  3. 常量和全域性標識,首尾為*,如*main-cli-fn*
  4. *x,標識內建變數,且經常值變化
  5. x?,標識斷言函式
  6. x!,標識產生副作用的函式
  7. x-,標識其將產生私有方法,如defn-deftest-
  8. _,標識可忽略的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

http://www.cnblogs.com/or2-/p/3579745.html

相關文章