clojure
我的新职位要求我熟悉Clojure语言 。 打算记录我在一系列帖子中学到的内容,以作为我的个人参考笔记。 作为副作用,我希望它对希望走同样道路的其他人也将是有益的。 考虑到我的大部分经验都来自OOP ,因此已经有大量出色的教程可供使用:因此,每篇文章都将专注于特定主题,该主题特定于Clojure。
这是在学习Clojure的焦点series.Other职位包括第2 个职位:
- 解码Clojure代码,让您不知所措
- 学习Clojure:应对动态类型化 (本文)
- 学习Clojure:arrow和doto宏
- 学习Clojure:动态调度
- 学习Clojure:依赖类型和基于合同的编程
- 学习Clojure:与Java流进行比较
- 关于学习Clojure的反馈:与Java流进行比较
- 学习Clojure:换能器
作为Clojure的新手,我的一个大问题是缺乏类型。 这不是Clojure特有的。 我错过了JavaScript,Groovy,Python等类型的信息。尽管我重视动态语言在编写脚本时的易用性,但我的主要任务仍然是开发常规应用程序。 考虑到这一点,我更愿意让编译器捕获与类型相关的错误:这意味着着眼于实际的业务功能,而不是编写测试来捕获这些错误。
尽管Clojure不提供语言语法中的类型,但其设计允许通过构造来构建类似的功能。 更好的是,有一个适当的命名规范spec来处理这个问题 。
自Clojure 1.9起,即可立即使用spec。 早期版本需要显式添加对类路径的依赖。
基本
要开始使用spec,只需在名称空间中要求 clojure.spec.alpha
包即可:
(ns ch.frankel.blog.clojure.spec
(:require [clojure.spec.alpha :as sparc]))
下一步是使用def
函数定义值的预期类型。 它接受两个参数:
# | 名称 | 描述 |
---|---|---|
1 | | Symbol name |
2 | | Predicate |
可能会有更多有效的参数值,但这足以开始。
对于简单的值,这非常简单:
(spec/def ::nil nil?) (1)
(spec/def ::bool boolean?) (2)
(spec/def ::string string?) (3)
- 将
::nil
定义为nil
值 - 将
::bool
定义为boolean
值 - 将
::string
定义为任何string
值
关键字是自我评估的符号标识符。 它们提供了非常快速的相等性测试。 像符号一样,它们具有名称和可选的名称空间,它们都是字符串。 前导“:”不是名称空间或名称的一部分。 — Clojure文档https://clojure.org/reference/data_structures#Keywords ::语法是限定关键字的快捷方式,该限定关键字使用当前名称空间进行完全限定。 例如,上面的:: bool解析为:ch.frankel.blog.clojure.spec / bool。
该技术不限于简单类型。 也可以将值限制为枚举:
(spec/def ::direction #{::NORTH ::EAST ::SOUTH ::WEST})
规格检查
定义规范后,有两种不同的使用方法。
-
valid?
函数返回一个boolean
,取决于值是否符合spec
例如 :资源 符合吗? 退货 (spec/valid? ::nil nil)
true
(spec/valid? ::string "f")
true
(spec/valid? ::nil "f")
false
(spec/valid? ::string nil)
false
-
conform?
函数返回:- 值是否符合
spec
- 或
clojure.spec.alpha/invalid
如果clojure.spec.alpha/invalid
资源 符合吗? 退货 (spec/conform? ::nil nil)
nil
(spec/conform? ::string "f")
"f"
(spec/conform? ::nil "f")
clojure.spec.alpha/invalid
(spec/conform? ::string nil)
clojure.spec.alpha/invalid
- 值是否符合
定制规格功能
上面的代码使用了现成的函数, 例如 nil?
string?
。 有很多类似的功能。 这是一个示例,摘自clojure.core
:
功能 | 检查参数是否为... |
---|---|
| a keyword (obviously…) |
| a symbol (obviously as well) |
| a symbol or a keyword |
| a |
| a |
虽然涵盖了一些用例,但并未涵盖许多特定用例。 在这种情况下,可以使用任何接受参数并返回boolean
函数。
这是一个检查参数是否为LocalDate
以及如何使用的函数:
(defn local-date?
"Check if the parameter is a java.time.LocalDate instance"
[x]
(instance? LocalDate x))
(spec/def ::date local-date?)
(spec/valid? ::date (LocalDate/of 2009 1 1)) (1)
(spec/valid? ::date "f") (2)
- 评估为
true
- 评估为
false
规范数据结构
现在我们知道如何指定简单的值,是时候指定更复杂的值了。 在Clojure中,对“实体”建模的一种常见方法是使用数据映射。
我最喜欢的示例是具有以下属性的Person
实体:
- 名字
- 姓
- 生日
可以使用keys
功能进行指定。 参数允许指定哪些键是必需的,哪些是可选的:
(spec/def ::first-name string?)
(spec/def ::last-name string?)
(spec/def ::birthdate local-date?)
(spec/def ::person (spec/keys :req [::first-name ::last-name] (1)
:opt [::birthdate])) (2)
- 必要值
- 可选值
以下是一些示例以及一些相关的有效性检查:
资源 | 退货 | 基本原理 |
---|---|---|
| | |
| | |
| | |
| | |
| | |
| | Additional entries are fine |
规格集合
下一步是使用规范来验证集合中元素的类型,就像Java中的泛型一样, 例如 List<T>
, Map<T>
或Set<T>
。 这可以通过附加功能来实现:
-
coll-of
为“标准”的Clojure集合,vector
或list
等。 -
map-of
地图
例如,指定LocalDate
列表非常简单:
(spec/def ::dates (spec/coll-of ::date))
同样,对于keyword
/ LocalDate
的映射:
(spec/def ::map-dates (spec/map-of keyword? ::date))
当然,它也适用于数据结构:
(spec/def ::map-persons (spec/map-of keyword? ::person))
结论
尽管Clojure是一种动态类型的语言,但可以通过使用spec
库来补充类型。 与使用任何静态类型的语言一样,它允许验证简单的类型,枚举,映射和集合。
clojure