你如何表示总和类型,也称为标记联合和
变异记录?类似于 Haskell 中的 Either a b 或
Either[+A, +B] 在 Scala 中。
Either 有两个用途:返回两种类型之一的值或
返回两个应该具有不同类型的相同类型的值
基于标签的语义。
第一次使用仅在使用静态类型系统时才重要。
Either 基本上是考虑到的最小解决方案
Haskell 类型系统的约束。使用动态类型系统,
你可以返回你想要的任何类型的值。不需要Either。
第二个用途很重要,但可以很简单地完成
以两种(或更多)方式:
-
{:tag :left :value 123} {:tag :right :value "hello"}
{:left 123} {:right "hello"}
我想确保的是 :tag 始终存在,并且它可以
只取指定值之一,对应的值为
始终具有相同的类型/行为,并且不能为零,并且有
很容易看出我处理了代码中的所有情况。
如果您想以静态方式确保这一点,Clojure 可能不是
你的语言。原因很简单:表达式没有类型
直到运行时——直到它们返回一个值。
宏不起作用的原因是在宏扩展时,您
没有运行时值——因此没有运行时类型。你有
编译时构造,如符号、原子、s 表达式等。你
可以eval 他们,但使用eval 被认为是不好的做法
原因有很多。
但是,我们可以在运行时做得很好。
- 我想确保的是,:tag 始终存在,
- 并且只能取指定值之一
- 并且相应的值始终具有相同的类型/行为
- 并且不能为零
- 还有一种简单的方法可以看出我处理了代码中的所有情况。
我的策略是将所有通常是静态的(在 Haskell 中)转换为运行时。让我们写一些代码。
;; let us define a union "type" (static type to runtime value)
(def either-string-number {:left java.lang.String :right java.lang.Number})
;; a constructor for a given type
(defn mk-value-of-union [union-type tag value]
(assert (union-type tag)) ; tag is valid
(assert (instance? (union-type tag) value)) ; value is of correct type
(assert value)
{:tag tag :value value :union-type union-type})
;; "conditional" to ensure that all the cases are handled
;; take a value and a map of tags to functions of one argument
;; if calls the function mapped to the appropriate tag
(defn union-case-fn [union-value tag-fn]
;; assert that we handle all cases
(assert (= (set (keys tag-fn))
(set (keys (:union-type union-value)))))
((tag-fn (:tag union-value)) (:value union-value)))
;; extra points for wrapping this in a macro
;; example
(def j (mk-value-of-union either-string-number :right 2))
(union-case-fn j {:left #(println "left: " %) :right #(println "right: " %)})
=> right: 2
(union-case-fn j {:left #(println "left: " %)})
=> AssertionError Assert failed: (= (set (keys tag-fn)) (set (keys (:union-type union-value))))
此代码使用以下惯用的 Clojure 构造:
- 数据驱动编程:创建代表“类型”的数据结构。这个值是不可变的和一流的,你可以使用整个语言来实现逻辑。这是我认为 Haskell 无法做到的事情:在运行时操作类型。
- 使用地图表示值。
- 高阶编程:将 fns 的映射传递给另一个函数。
如果您使用Either 进行多态性,您可以选择使用协议。否则,如果您对标签感兴趣,{:tag :left :value 123} 形式的东西是最惯用的。你会经常看到这样的东西:
;; let's say we have a function that may generate an error or succeed
(defn somefunction []
...
(if (some error condition)
{:status :error :message "Really bad error occurred."}
{:status :success :result [1 2 3]}))
;; then you can check the status
(let [r (somefunction)]
(case (:status r)
:error
(println "Error: " (:message r))
:success
(do-something-else (:result r))
;; default
(println "Don't know what to do!")))