• (cljs/run-at (JSVM. :browser) "简单类型可不简单啊~")


    前言

     每逢学习一个新的语言时总要先了解这门语言支持的数据类型,因为数据类型决定这门语言所针对的问题域,像Bash那样内置只支持字符串的脚步明显就是用于文本处理啦。而数据类型又分为标量类型(Scalar)、结构类型(Struct)和集合类型(Collection),标题中的简单类型实质就是指标量类型。
     cljs中内置的标量类型比js的丰富得多,一方面方便了操作,另一个方面增加了学习成本,因此从js转向cljs时可能会略感不适,下面我们一起来认识吧!

    标量类型一览

    ;; 空值/空集
    nil
    
    ;; 字符串,必须使用双引号包裹
    "I am a string!"
    
    ;; 字符,以斜杆开头
    &
    
    ewline
    
    ;; 布尔类型(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

  • 相关阅读:
    Python生成器
    Python迭代器
    模块
    列表推倒式
    内置函数 lambda表达式
    函数
    global、nonlocal、迭代器
    练习
    编码数组字典
    字典
  • 原文地址:https://www.cnblogs.com/fsjohnhuang/p/7119333.html
Copyright © 2020-2023  润新知