文章出處

前言

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


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()