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

简介: 每逢学习一个新的语言时总要先了解这门语言支持的数据类型,因为数据类型决定这门语言所针对的问题域,像Bash那样内置只支持字符串的脚步明显就是用于文本处理啦。而数据类型又分为标量类型(Scalar)、结构类型(Struct)和集合类型(Collection),标题中的简单类型实质就是指标量类型。

前言

 每逢学习一个新的语言时总要先了解这门语言支持的数据类型,因为数据类型决定这门语言所针对的问题域,像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")
  1. 自动扩展为以当前命名空间为前缀
(ns cljs.user)
;; 自动扩展为以当前命名空间为前缀的keywork
::keyword ;;=> :cljs.user/keyword
  1. 自动扩展为
;; 自动查找以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

目录
相关文章
|
Linux
vsftpd 修改指定端口
vsftpd的一般默认端口为21,一般来说端口21不太方便开放,因此多数时间需要修改指定默认的端口。
2495 0
|
5月前
|
设计模式 网络协议 Java
09.接口vs抽象类比较
本文详细对比了接口与抽象类的区别及应用场景,涵盖两者的基本概念、特性以及设计思想。通过具体案例分析,如日志记录和过滤器功能,阐明抽象类适用于代码复用(is-a关系),而接口侧重解耦和行为定义(has-a关系)。此外,还探讨了如何在不支持接口或抽象类的语言中模拟其实现,并总结了选择两者的判断标准。文章结合实际开发场景,提供了清晰的指导,帮助开发者更好地理解与应用这两种核心面向对象概念。
260 26
|
存储 Ubuntu 网络安全
在Ubuntu系统下通过Caddy实现LXD的安装与部署
通过上述步骤,您可以在Ubuntu系统下通过Caddy实现LXD的安装与部署。这种方法不仅可以提高容器管理的效率,还可以借助Caddy的自动SSL管理功能提升安全性。
276 0
|
11月前
|
Docker 容器
容器的日志
【10月更文挑战第31天】
507 68
|
7月前
|
存储 安全 数据安全/隐私保护
智能手表与代理IP:守护你的运动数据隐私
在数字化时代,智能手表不仅记录运动、心率和睡眠数据,还提供通讯、支付功能。然而,其普及也带来了数据隐私风险。本文探讨智能手表与代理IP结合如何守护运动数据隐私。通过案例说明,介绍代理IP隐藏真实IP、加密传输的作用,并讨论其局限性及应对措施,展望未来技术创新和法律法规完善对数据隐私保护的推动。
160 0
|
11月前
图片的大小
【10月更文挑战第7天】
731 1
|
SQL 存储 分布式计算
HDFS数据(跨集群)迁移
HDFS数据(跨集群)迁移
|
监控 安全 Linux
虚拟专用网络(VPN):远程访问与点对点连接及其在Linux中的IPSec实现与日志管理
虚拟专用网络(VPN):远程访问与点对点连接及其在Linux中的IPSec实现与日志管理
534 0
|
物联网 芯片 开发者
低功耗技术在智能硬件上的应用
随着芯片技术的不断发展,CPU的主频越来越高,随之而来的高功耗及发热等问题也日益显现出来,因此低功耗设计也成为了智能硬件中必须面对的重大课题。业界在低功耗的设计方面有许多优秀的实践案例,值得我们借鉴和学习,本文总结了一些经典的低功耗设计方法,同时也会详细阐述AliOS Things在IPC中采用的低功耗方案。
低功耗技术在智能硬件上的应用
实测Hutool的雪花算法8G内存跑到7600万条OOM
实测Hutool的雪花算法8G内存跑到7600万条OOM