本文来自网易云社区

作者:李诺

" Clojure is elegant and pragmatic; it helps me focus more on solving business problems."

不同于Java这类静态语言, Clojure是动态语言,动态类型意味着这些类型会在代码运行时由Clojure动态的推导出来,编译时不作任何限制。

user=> (defn f1 [a b] (+ "1" 2))#'user/f1
user=> (f1 1 2)

ClassCastException java.lang.String cannot be cast to java.lang.Number  clojure.lang.Numbers.add (Numbers.java:128)

对比上面的两端代码,即使函数f1中含有类型有误的表达式(+ "1" 2)也是可以定义的。然而一旦运行的时候(我们随意给了两个没有用到的参数),函数+会尝试将"1"和2投射(cast)到Java中的Number类,这时候代码中的类型错误就会以ClassCastException抛出来,因为字符串"1"是无法被投射到Number类的。

注意: 投射(cast)和转换(convert)并不一样,cast一般是将一个子类的成员投射到它的超类上,而像Javascript中"1" + 2 = 3所做的就是隐式转换了。Clojure的原生函数没有使用隐式转换。

所以我们在写代码的时候一般不需要给定数据类型,但这不意味着类型就不重要了。

为了更好地认识Clojure的类型,首先要会用一个函数: type

user=> (doc type)
-------------------------
clojure.core/type([x])
  Returns the :type metadata of x, or its Class if nonenil

我们可以用这个函数获取定义中的元数据中的:type项,如果没有这项,那么就返回它的类名。

基本数据类型

我们先来看一些常见的值被推导的默认类型:

数值

  • 整数 1,2,3...

user=> (type 1)
java.lang.Long

Clojure的整数默认使用Java中的Long基础类型,

  • 小数 1.414,π...

user=> (type Math/PI)
java.lang.Double

小数使用Java中的Double类型。

如果我们想要使用其他一些java中的基本类型的整数或者浮点数,我们可以使用

user=> (type (int 1))
java.lang.Integer user=> (type (float 3.14))
java.lang.Float

这类强制转换(Coerce)函数来生成。

有兴趣的同学可以去试试  (type 1111111111111111111111111111111111111111)  和  (type (/ 1 2)) 是什么类型的。

字符串

user=> (type \a)
java.lang.Character user=> (type "Hello")
java.lang.String

字符和字符串同样也是来自Java。

关键词

可以看到,数字,字符串都使用的是Java的类型,而Clojure中也有一些独有的内建基础类型,如关键词,

user=> (type :hello)
clojure.lang.Keyword

如类似:a,:book,:code这样的关键词。关键词的相等性测试非常快,所以关键词常见的使用场景是map类型(之后会详细介绍)中的key。

在下面的这个benchmark中,我们对比了从两种map中获取key对应的value的调用时间,使用关键词做key的map比另一个使用字符串做key的map快了50%,

insight.main=> (def t {:hello 1})
:hella#'insight.main/t
insight.main=> (quick-bench (get t :hello))WARNING: Final GC required 76.32000953339649 % of runtimeEvaluation count : 55186818 in 6 samples of 9197803 calls.             Execution time mean : 10.005364 ns    Execution time std-deviation : 1.900011 ns   Execution time lower quantile : 8.087005 ns ( 2.5%)
   Execution time upper quantile : 12.200609 ns (97.5%)
                   Overhead used : 2.154486 ns
nil
insight.main=> (def t1 {"hello" 1})
#'insight.main/t1
insight.main=> (quick-bench (get t1 "hello"))WARNING: Final GC required 71.39921475186507 % of runtimeEvaluation count : 39262944 in 6 samples of 6543824 calls.             Execution time mean : 15.356626 ns    Execution time std-deviation : 0.589740 ns   Execution time lower quantile : 14.530609 ns ( 2.5%)
   Execution time upper quantile : 16.016271 ns (97.5%)
                   Overhead used : 2.154486 nsFound 1 outliers in 6 samples (16.6667 %)
    low-severe     1 (16.6667 %)
 Variance from outliers : 13.8889 % Variance is moderately inflated by outliersnil
insight.main=>

集合(Collections)

要看懂Clojure的代码,只有前面的这些类型还不够,还需要一些基本的数据结构,如Vector,List,Map,Set等等,因为Clojure的代码即数据。

List

List的是函数式语言里常见的一种数据结构,我们可以有两种基本的方法构成

user=> (type '(1 2 3))
clojure.lang.PersistentListuser=> (type (list 1 2 3))
clojure.lang.PersistentList

事实上,list的显示是不需要'(quote)或者list的

user=> (map inc '(1 2 3))
(2 3 4)

那为什么在构建list的时候不能这样写呢?其实原因很简单,

list是构成Clojure代码的重要组成部分!

回看Clojure这类Lisp语言的语法,到处可见如(type 1),(+ 1 2)这样的list形式的代码,其实形如

(operator operand1 operand2 ... operandn)

这样的代码中,第一项operator会被当做可执行的函数, 后面的n项operands则是operator的参数。简单的来说,就是以list的形式写的表达式,默认会被当成一个函数应用(function application)的表达式求值(evaluate), 而',也就是quote函数,能阻止求值。

求值:

user=> (list 1 (+ 1 2) 3)
(1 3 3)

不求值:

user=> '(1 (+ 1 2) 3)
(1 (+ 1 2) 3)

Clojure语言具有同像性(homoiconicity),也就是说,代码一般是以数据的形式组织在一起的,这让操作代码变得和操作数据一样容易,方便了元编程(Clojure中可以编写宏)。

回到list的类型上,list在Clojure里面被叫做  PersistentList  也就是"持久化的list",是不可变数据类型的一种。Clojure的持久化的数据结构方便了在数据不可变的前提下,对于基于已有数据结构的“修改”操作(数据不可变,“修改”本质上来说是新数据的创建),能够尽可能的结构共享(structure sharing)。

持久化list的结构共享可以用下面的这个例子解释(来自Wiki):

(def xs '(0 1 2))
(def ys '(3 4 5))

将这两个list做联结合并(concat)

(def zs (concat xs ys))

可以看到xs和ys都存留了下来,在内存中只有xs做了复制,而ys被共享了,因为图中蓝色的部分和xs并不完全一样

Vector

Vector是Clojure里一般被理解作类似Java里的ArrayList的数据结构。

user=> (type [0 1 2])
clojure.lang.PersistentVector

Vector同样也是持久化的数据类型。

事实上Vector是以树形结构储存的数组,它每一层最多有32个子节点,所以它的随机访问是O(log32n)的(也就是O(logn))。有兴趣的朋友可以看Understanding Clojure's Persistent Vectors, pt. 1里关于如何增加,删减元素的非常详尽的介绍。

类似地,我们也可以用

user=> (vector 1 2 3)[1 2 3]

来组成Vector,或是用

user=> (vec '(1 2 3))[1 2 3]

将list或其他结构转换成vector。

我们一般也可以把vector视作和array类似的,添加,更新,查询都只要O(1)的结构。

Vector也是一个经常用于组织代码,如在函数定义中

(defn plus [a b]
  (+ a b))

函数名字和函数体之间的函数参数就是以vector的形式给出的。其他的还有let中

(defn f1 [a b]
  (let [sum (+ a b)
        diff (- a b)]
    (* sum diff)))

我们需要给出一个有偶数对成员的且满足一些特殊条件的vector。

Set

在Clojure中,Set也是内建的数据结构,有特别的书写形式

user=> (type #{1 2 3})clojure.lang.PersistentHashSet

Set的成员具有唯一性,我们可以直接把它当做一个函数使用,判断一个值是否是集合中的一员

user=> (def s1 #{1 3 5 7})#'user/s1user=> (s1 3)3user=> (s1 4)
nil

当这个成员存在于集合中,会返回它自身,不然则返回nil(等同于null)。

Map

Map是Clojure中使用频率非常高的数据结构,对于简单的map,我们可以直接在{``}中写偶数个成员, key和value均不用使用同一类型,

user=> (type {:name "doge" "age" 2})
clojure.lang.PersistentArrayMap

默认的map类型为ArrayMap, 它的结构和Vector比较类似,里面的条目是按照创建的顺序排列的,如果要创建HashMap,我们可以用

user=> (hash-map :name "X" :age "10")
{:age "10", :name "X"} user=> (type (hash-map :name "X" :age "10"))
clojure.lang.PersistentHashMap

Map同样可以被当做函数执行,

user=> (def m1 {:name "X" :age "10"})#'user/m1user=> (m1 :name)"X"user=> (m1 :title)niluser=> (m1 :title "default value")"default value"

会在map中寻找第一个给入的参数作为key的value,如果没有,默认返回nil,如果有第二个参数,会代替nil返回。

Clojure给Map的操作提供了不少便捷的函数,我们可以用get来获取某个key对应的value

user=> (def m1 {:age 10 :name {:firstname "John" :surname "Smith"}})#'user/m1user=> (get m1 :name)
{:firstname "John", :surname "Smith"}

而当我们需要查询更深层的value时候,我们可以使用get-in,搭配上一个按顺序给出的keys的vector

user=> (get-in m1 [:name :surname])"Smith"

当我们要插入或是覆盖某个key对应的value时,我们可以使用assoc

user=> (assoc m1 :height 150)
{:age 10, :name {:firstname "John", :surname "Smith"}, :height 150}

如果需要更新某个value,可以使用update

user=> (update m1 :age (fn [x] (+ x 1)))
{:age 11, :name {:firstname "John", :surname "Smith"}}

update和assoc的区别在于update可以把原来的值作为生成新的值的一个参数,其实他也等同于你用get之后再用assoc,用下面的等式来表达

(update m k f) = (assoc m k (f (get m k)))

在使用的时候,使用最贴合代码逻辑的写法,会更有利于代码的可读性和可维护性。后面的写法如果要等于左边m和k都出现了两次,潜在的增加了修改时的风险。但是后面的写法有时会显得更为灵活。

和get类似,Clojure也提供了assoc-in和update-in,配合上一个常用的宏->使用,可以让map的操作写起来非常简洁直观

user=> (-> {:name {:fullname "Xiao Wang"} :from {:Country "China" :City "Beijing"}}  #_=>     (assoc :age 30)
  #_=>     (assoc-in [:from :City] "Shanghai")
  #_=>     (update-in [:name :fullname] (fn [full-name] (clojure.string/split full-name #" ")))
  #_=>     ){:name {:fullname ["Xiao" "Wang"]}, :from {:Country "China", :City "Shanghai"}, :age 30}

总结

认识了这些基本的数据类型之后,Clojure的代码将会变得非常容易上手,欢迎感兴趣的朋友们尝试Clojure,Clojure的简洁和直观一定能够让你印象深刻,让你能够更focus在解决真正的问题上。安装的方法可以参考之前我写的这篇

答案

user=> (type 111111111111111111111111111111111111111111111111111)
clojure.lang.BigInt user=> (type (/ 1 2))
clojure.lang.Ratio

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者李诺品授权发布。

相关文章:
【推荐】 餐饮行业的利器——大数据
【推荐】 如何准确又通俗易懂地解释大数据及其应用价值?

Clojure基础课程2-Clojure中的数据长啥样?的更多相关文章

  1. (大数据工程师学习路径)第四步 SQL基础课程----创建数据库并插入数据

    一.练习内容 1.新建数据库 首先,我们创建一个数据库,给它一个名字,比如“mysql_shiyan”,以后的几次实验也是对mysql_shiyan这个数据库进行操作. 语句格式为“CREATE DA ...

  2. [WPF 基础知识系列] —— 绑定中的数据校验Vaildation

    前言: 只要是有表单存在,那么就有可能有对数据的校验需求.如:判断是否为整数.判断电子邮件格式等等. WPF采用一种全新的方式 - Binding,来实现前台显示与后台数据进行交互,当然数据校验方式也 ...

  3. 『无为则无心』Python基础 — 11、Python中的数据类型转换

    目录 1.为什么要进行数据类型转换 2.数据类型转换本质 3.数据类型转换用到的函数 4.常用数据类型转换的函数 (1)int()函数 (2)float()函数 (3)str()函数 (4)bool( ...

  4. laravel基础课程---16、数据迁移(数据库迁移是什么)

    laravel基础课程---16.数据迁移(数据库迁移是什么) 一.总结 一句话总结: 是什么:数据库迁移就像是[数据库的版本控制],可以让你的团队轻松修改并共享应用程序的数据库结构. 使用场景:解决 ...

  5. laravel基础课程---3、路由(Laravel中的常见路由有哪几种)

    laravel基础课程---3.路由(Laravel中的常见路由有哪几种) 一.总结 一句话总结: 6种:post,get,put,patch,delete,options Route::get($u ...

  6. clojure基础入门(一)

    最近在看storm的源码,就学习分享下clojure语法. 阅读目录: 概述 变量 运算符 流程控制 总结 概述 clojure是一种运行在JVM上的Lisp方言,属于函数式编程范式,它和java可以 ...

  7. (大数据工程师学习路径)第四步 SQL基础课程----select详解

    准备 在正式开始本内容之前,需要先从github下载相关代码,搭建好一个名为mysql_shiyan的数据库(有三张表:department,employee,project),并向其中插入数据. 具 ...

  8. 归纳从文件中读取数据的六种方法-JAVA IO基础总结第2篇

    在上一篇文章中,我为大家介绍了<5种创建文件并写入文件数据的方法>,本节我们为大家来介绍6种从文件中读取数据的方法. 另外为了方便大家理解,我为这一篇文章录制了对应的视频:总结java从文 ...

  9. Java基础知识强化之IO流笔记45:IO流练习之 把集合中的数据存储到文本文件案例

    1. 把集合中的数据存储到文本文件案例:    需求:把ArrayList集合中的字符串数据存储到文本文件 ? (1)分析:通过题目的意思我们可以知道如下的一些内容,ArrayList集合里存储的是字 ...

随机推荐

  1. BZOJ3790:神奇项链(Manacher)

    Description 母亲节就要到了,小 H 准备送给她一个特殊的项链.这个项链可以看作一个用小写字 母组成的字符串,每个小写字母表示一种颜色.为了制作这个项链,小 H 购买了两个机器.第一个机器可 ...

  2. [POI2015]KIN

    题目 感觉这种题好套路啊,怎么又是这个做法 癌不过怎么没有人和我一样些写套路做法,那干脆来写个题解吧 我们考虑枚举区间的右端点,这样我们只需要考虑从\(1\)到\(i\)的最大区间就好了 由于我们选择 ...

  3. git branch 进入编辑状态

    命令行输入git branch,发现进入编辑状态,都要:wq,非常不方便,这样配置 git config --global core.pager ''

  4. CF739B Alyona and a tree

    嘟嘟嘟 前缀和+倍增+树上差分 假设\(v\)是\(u\)子树中的一个点,那么\(u\)能控制\(v\)的条件是受\(v\)的权值的限制,而并非\(u\).因此我们就能想到计算每一个点的贡献,即\(v ...

  5. 移动端 Touch 事件

    在移动端页面开发时,常常会用到touch事件,比如左滑右滑的轮播等.常用的触摸事件有touchstart,touchmove,touchend. 每个事件包含下面三个用于跟踪虎摸的属性: touche ...

  6. es6之函数扩展与对象扩展

    一.函数扩展 1.参数默认值 参数有默认值,后面不可以再加没有默认值的变量.如以下test函数中,不可以加写成 function test(x,y="word",z){ } fun ...

  7. idea教程视频以及常用插件整理

    最近在同事的强烈安利下把eclipse换成idea了,本以为需要经历一个艰难的过渡期,谁知道不到3天就深感回不去了. 哎,只能说有时候人的惰性是多么可怕! idea实在是太太太强大了. 不要再问原因. ...

  8. STS使用git下载项目代码

    在自己的eclipse 上安装git 插件,一般都自带了现在. 4.选择Clone URI 5.下一步输入刚才的复制的路劲,填写自己的github 账户名密码即可 6.选择要克隆的分支 7.设置本地g ...

  9. 【luogu P1825 [USACO11OPEN]玉米田迷宫Corn Maze】 题解

    题目链接:https://www.luogu.org/problemnew/show/P1825 带有传送门的迷宫问题 #include <cstdio> #include <cst ...

  10. DBUtils连接池,websocket

    1.mysql数据库连接池 概念:数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放. 这样 ...