clojure和scala
在描述如何处理动态类型时,我们使用了规范库。 该库不是类型的真正替代-检查是在运行时而不是编译时执行的。 另一方面,它可以超越单纯的类型,包括模拟依赖类型和按合同编程。
这是在学习Clojure的焦点series.Other职位包括5 个职位:
- 解码Clojure代码,让您不知所措
- 学习Clojure:应对动态打字
- 学习Clojure:arrow和doto宏
- 学习Clojure:动态调度
- 学习Clojure:依赖类型和基于契约的编程 (本文)
- 学习Clojure:与Java流进行比较
- 关于学习Clojure的反馈:与Java流进行比较
- 学习Clojure:换能器
依赖类型
让我们从定义什么是依赖类型开始:
在计算机科学和逻辑中,从属类型是其定义取决于值的类型。 “整数对”是一种类型。 由于对值的依赖性,“第二对大于第一对的整数对”是从属类型。
https://zh.wikipedia.org/wiki/Dependent_type
让我们尝试使用spec对以上定义中提到的类型进行建模。
显然,“整数对”可以建模为大小为2的集合,其中包含按设置顺序包含int
。
我们将使用增量方法:
- 第一个模型
int
的集合 - 然后在大小上加一个限制
- 最后检查价值订购
让我们开始对int
的集合进行建模。 在第一篇文章中看到的coll-of
函数非常适合指定这样的集合。
请记住,其参数是应用于集合中每个元素的谓词
(s/def ::ints (s/coll-of int?))
(s/valid? ::ints [2 "2"]) ; false
(s/valid? ::ints ["4" "4"]) ; false
(s/valid? ::ints [3 5]) ; true
(s/valid? ::ints [2]) ; true
(s/valid? ::ints [2 2 2]) ; true
现在,让我们将元素数量限制为2,以形成一对。 相关的代码非常简单,因为函数coll-of
允许:count
键指定所需的元素数量:
(s/def ::pair-of-ints (s/coll-of int? :count 2)) (1)
(s/valid? ::pair-of-ints [2 2]) ; true
(s/valid? ::pair-of-ints [4 4]) ; true
(s/valid? ::pair-of-ints [3 5]) ; true
(s/valid? ::pair-of-ints [2]) ; false
(s/valid? ::pair-of-ints [2 2 2]) ; false
- 任何两个
int
现在该添加最终要求了:第二个值必须大于第一个值。 显然这是一个从属类型,因为它意味着检查值。
- 这需要为此创建谓词检查:
(defn ordered? [pair] (> (last pair) (first pair)))
- 然后,我们需要组成这个自定义谓词和一个开箱即用的
coll-of
。 通过适当命名的布尔宏and
和or
可以使这种组合成为可能。(s/def ::dependent (s/and (s/coll-of int? :count 2) (1) ordered?)) (s/valid? ::dependent [2 2]) ; false (s/valid? ::dependent [3 4]) ; true (s/valid? ::dependent [2 1]) ; false (s/valid? ::dependent [1 2 3]) ; false
就是这样,我们定义了(运行时)依赖类型。 所有构建块均可用于构建更复杂的类型。
但是,这还不是全部。 类型-不管是否依赖,本身都不有用。
如果我们可以将它们与功能结合起来怎么办?
基于合同的编程
仅像上一节中那样设置依赖类型不是很有用:它需要在每个变量上显式调用(s/valid)
,这很笨拙。 不过,Clojure还提供了另一个选项。
我已经写过关于在Java(Java和Kotlin) 上使用JVM进行合约式编程( 又名基于合约式编程)的文章。 Clojure允许在执行函数主体之前和之后调用不确定数量的函数。 这可以通过定义一个特定于所需函数的映射来实现:pre
和:post
分别接受要在函数之前和之后执行的谓词数组。
将这些前/后执行与规范结合起来,即可实现基于合同的编程。
让我们创建一个add
函数,将上述两对相加。 该函数应类似于:
(defn add
[p1, p2]
"Add two pairs"
(let [s1 (+ (first p1) (first p2))
s2 (+ (last p1) (last p2))]
[s1 s2]))
-
p1
和p2
都必须通过::dependent
进行验证。 转换为:{:pre [(s/valid? ::dependent p1), (s/valid? ::dependent p2)]}
- 返回的值也应使用相同的谓词进行验证,因为如果两者的第一项都低于第二项,则添加项不应对此进行更改。 要引用该值,请使用
%
:{:post (s/valid? ::dependent %)}
最后的功能是:
(defn add
[p1, p2]
{:pre [(s/valid? ::dependent p1),
(s/valid? ::dependent p2)]
:post (s/valid? ::dependent %)}
(let [s1 (+ (first p1) (first p2))
s2 (+ (last p1) (last p2))]
[s1 s2]))
它可以这样称呼:
(add [1 2] [3 4]) ; [4 6]
(add [2] [3 4]) ; AssertionError
(add ["1" 2] [3 4]) ; AssertionError
(add [1 2] 4) ; AssertionError
结论
在这篇文章中,我们描述了如何超越单纯的类型,以及对具有依赖类型的值施加约束。 结合按合同进行编程,这对使代码更可靠确实有帮助。
clojure和scala