您如何表示总和类型,也称为标记联合和变体记录?类似于Either a b
Haskell 或
Either[+A, +B]
Scala 中的东西。
Either
有两个用途:返回两种类型之一的值,或者返回两个相同类型的值,它们应该基于标签具有不同的语义。
第一次使用仅在使用静态类型系统时才重要。
Either
考虑到 Haskell 类型系统的约束,基本上是可能的最小解决方案。使用动态类型系统,您可以返回所需的任何类型的值。Either
不需要。
第二种用途很重要,但可以通过两种(或更多)方式非常简单地完成:
{:tag :left :value 123} {:tag :right :value "hello"}
{:left 123} {:right "hello"}
我想确保的是, :tag 始终存在,并且它只能采用指定值之一,并且相应的值始终具有相同的类型/行为并且不能为 nil,并且有一种简单的方法可以看到我处理了代码中的所有情况。
如果您想以静态方式确保这一点,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!")))