本文来自网易云社区

作者:李诺

" 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. JAVA二叉树递归构造、二叉树普通遍历及递归遍历

    二叉树类: package com.antis.tree; public class BinaryTree { int data; //根节点数据 BinaryTree left; //左子树 Bin ...

  2. C# Windows服务的安装和卸载批处理

    @ECHO "请按任意键开始安装后台服务. . ."@ECHO "清理原有服务项. . ."%SystemRoot%\Microsoft.NET\Framewo ...

  3. 最简单的PS渐变导入方法 photoshop渐变插件素材导入教程

    photoshop渐变插件素材可以让用户更好更直接,更快速地设计出自己想要的效果作品.网上有多种多样的ps渐变,那么Mac版Ps渐变怎么导入呢?这里我来和大家分享一下photoshop渐变插件素材导入 ...

  4. window使用结束进程

    在cmd中输入下面信息: 1. 查看所有进程占用的端口 :netstat -ano 2.查看占用指定端口的程序:netstat –ano|findstr 指定端口号 3.杀死相关的进程: 方法一:使用 ...

  5. [后台管理]一套用vue搭建的框架

    1.提前的准备工作 前端开发工具有许多,当下流行的sublime等等都是前端比较受欢迎的,nodeJS和Vue等都是前端框架搭建流行的一套 安装nodeJS 设置环境变量 安装Visual Studi ...

  6. PAT——1002. 写出这个数

    读入一个自然数n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式:每个测试输入包含1个测试用例,即给出自然数n的值.这里保证n小于10100. 输出格式:在一行内输出n的各位数字之和的每 ...

  7. 【题解】洛谷P4391 [BOI2009] Radio Transmission(KMP)

    洛谷P4391:https://www.luogu.org/problemnew/show/P4391 思路 对于给定的字符串 运用KMP思想 设P[x]为前x个字符前缀和后缀相同的最长长度 则对于题 ...

  8. Faster Alternatives to glReadPixels and glTexImage2D in OpenGL ES

    In the development of Shou, I’ve been using GLSL with NEON to manipulate image rotation, scaling and ...

  9. C#中Lambda表达式类型Expression不接受lambda函数

    在EF Core中我们经常会用System.Linq.Expressions系统命名空间的Expression<TDelegate>类型来作为EF Core的查询条件,比如: using ...

  10. Spring Bean自动注册的实现方案

    这里Spring管理的Bean,可以认为是一个个的Service,每个Service都是一个服务接口 自动注册Service的好处: 1.根据指定的name/id获取对应的Service,实现简单工厂 ...