已编辑。我现在的问题是:在静态类型语言中,通常使用什么惯用的 Clojure 构造而不是 sum 类型?到目前为止的共识:如果行为可以统一,则使用协议(protocol),否则使用标记对/映射,在前置条件和后置条件中放置必要的断言。
Clojure 提供了多种表示产品类型的方法:向量、 map 、记录...,但是您如何表示 sum types ,也称为标记联合和变体记录?像 Either a b
在 Haskell 或 Either[+A, +B]
在斯卡拉。
我首先想到的是一张带有特殊标签的 map :{:tag :left :value a}
, 但是所有代码都将被 (:tag value)
上的条件污染并在不存在的情况下处理特殊情况...我想确保的是 :tag
始终存在,并且只能取指定值之一,并且对应的值始终具有相同的类型/行为,不能是nil
,并且有一种简单的方法可以看出我处理了代码中的所有情况。
我可以想到 defrecord
行中的宏,但对于总和类型:
; it creates a special record type and some helper functions
(defvariant Either
left Foo
right :bar)
; user.Either
(def x (left (Foo. "foo"))) ;; factory functions for every variant
; #user.Either{:variant :left :value #user.Foo{:name "foo"}}
(def y (right (Foo. "bar"))) ;; factory functions check types
; SomeException...
(def y (right ^{:type :bar} ()))
; #user.Either{:variant :right :value ()}
(variants x) ;; list of all possible options is intrinsic to the value
; [:left :right]
这样的事情是否已经存在? (回答:没有)。
最佳答案
how do you represent sum types, also known as tagged unions and variant records? Something like
Either a b
in Haskell orEither[+A, +B]
in Scala.
Either
有两个用途:返回两种类型之一的值或返回两个应该具有不同类型的相同类型的值
基于标签的语义。
第一次使用仅在使用静态类型系统时才重要。
Either
基本上是可能的最小解决方案Haskell 类型系统的约束。使用动态类型系统,
你可以返回你想要的任何类型的值。
Either
不需要。第二种用途很重要,但可以很简单地完成
以两种(或更多)方式:
{:tag :left :value 123} {:tag :right :value "hello"}
{:left 123} {:right "hello"}
What I'd like to ensure, is that :tag is always there, and it can take only one of the specified values, and corresponding value is consistently of the same type/behaviour and cannot be nil, and there is an easy way to see that I took care of all cases in the code.
如果您想以静态方式确保这一点,Clojure 可能不是
你的语言。原因很简单:表达式没有类型
直到运行时——直到它们返回一个值。
宏不起作用的原因是在宏扩展时,您
没有运行时值——因此没有运行时类型。你有
编译时构造,如符号、原子、s 表达式等。你
可以
eval
他们,但使用 eval
被认为是不好的做法数量的原因。
但是,我们可以在运行时做得很好。
我的策略是将所有通常是静态的(在 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 构造:
如果您正在使用
Either
,您可以选择使用协议(protocol)。为多态性。否则,如果您对标签感兴趣,格式为 {: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!")))
关于types - 在 Clojure 中表示 sum 类型(a b)的惯用方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10947636/