Types Clojure中表示和类型(a或b)的惯用方法
编辑。我现在的问题是:在静态类型语言中,通常使用什么惯用Clojure构造来代替sum类型?到目前为止的共识:如果行为可以统一,则使用协议,否则使用标记对/映射,在前置和后置条件中放置必要的断言 Clojure提供了许多表示产品类型的方法:向量、映射、记录……但如何表示,也称为标记的联合和变体记录?类似于Haskell中的a b或Scala中的[+a,+b] 我想到的第一件事是一个带有特殊标记的地图:Types Clojure中表示和类型(a或b)的惯用方法,types,clojure,discriminated-union,Types,Clojure,Discriminated Union,编辑。我现在的问题是:在静态类型语言中,通常使用什么惯用Clojure构造来代替sum类型?到目前为止的共识:如果行为可以统一,则使用协议,否则使用标记对/映射,在前置和后置条件中放置必要的断言 Clojure提供了许多表示产品类型的方法:向量、映射、记录……但如何表示,也称为标记的联合和变体记录?类似于Haskell中的a b或Scala中的[+a,+b] 我想到的第一件事是一个带有特殊标记的地图:{:tag:left:value a},但是所有的代码都会被(:tag-value)上的条件所污
{:tag:left:value a}
,但是所有的代码都会被(:tag-value)
上的条件所污染,如果它不存在,则会处理特殊情况。。。我想确保的是,:tag
始终存在,它只能接受一个指定的值,相应的值始终具有相同的类型/行为,不能是nil
,并且有一种简单的方法可以看出我处理了代码中的所有情况
我可以在defrecord
行中想到一个宏,但对于sum类型:
; 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]
这样的事情已经存在了吗?(回答:没有)。没有,clojure现在还没有这样的东西。虽然您可以实现它,但在IMO中,这种类型似乎更适合静态类型的语言,在clojure这样的动态环境中不会给您带来太多好处 这种方法在某些语言中效果很好的原因是您对结果进行分派(通常按类型),即使用结果的某些属性(通常为类型)来决定下一步要做什么 因此,您需要了解在clojure中如何进行调度
nil
值在不同的地方是特殊情况,可以用作“Maybe”的“None”部分。例如,if let
非常有用case
或cond
。更有用的是,您可以将它们包装在某种宏中,以您想要的方式工作通常,动态类型语言中的和类型表示为:
- 标记对(例如,带有代表构造函数的标记的产品类型)
- 在运行时对标记进行案例分析以执行分派
或者或者可能是——因此您只需查看标记就可以知道它是左还是右
在动态类型设置中,必须首先执行运行时类型分析(查看您拥有的值的类型),然后执行构造函数的案例分析(查看您拥有的值的风格)
一种方法是为每种类型的每个构造函数分配一个唯一的标记
在某种程度上,您可以将动态类型视为将所有值放入一个和类型,将所有类型分析推迟到运行时测试
我想确保的是:标记始终存在,它只能接受一个指定的值,相应的值始终具有相同的类型/行为,不能为零,并且有一种简单的方法可以看出我处理了代码中的所有情况
顺便说一句,这基本上是对静态类型系统的描述
如何表示和类型,也称为标记的并集和
变异记录?类似于Haskell中的a b或
Scala中的[+A,+B]
或者
有两种用途:返回两种类型之一的值或者
返回两个相同类型的值,它们应该具有不同的
基于标记的语义
只有在使用静态类型系统时,第一次使用才很重要。
或者
基本上是给定
Haskell型系统的约束。采用动态类型系统,
您可以返回任何类型的值<代码>不需要任何一个
第二个用途很重要,但可以非常简单地完成
以两种(或更多)方式:
{:tag:left:value 123}{:tag:right:value“hello”}
{:left 123}{:right“hello”}
我想确保的是:标签总是在那里,它可以
只取一个指定值,对应的值为
始终具有相同的类型/行为,且不能为零,且
这是一个简单的方法,可以看出我处理了代码中的所有案例
如果您想静态地确保这一点,Clojure可能不是
你的语言。原因很简单:表达式没有类型
直到运行时——直到它们返回一个值
宏无法工作的原因是,在宏展开时
没有运行时值,因此也没有运行时类型。你有
编译时构造,如符号、原子、六进制表达式等
可以eval
对它们进行评估,但使用eval
被认为是一种不好的做法
原因很多
然而,我们可以在运行时做得很好
- 我想确保的是:标签始终存在
- 并且它只能接受指定值中的一个
- 相应的值为cons
;; 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))))
;; 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!")))
(defprotocol Fooable
(foo [x]))
(defrecord AType [avalue]
Fooable
(foo [x]
(println (str "A value: " (:avalue x)))))
(defrecord BType [bvalue]
Fooable
(foo [x]
(println (str "B value: " (:bvalue x)))))
(foo (AType. "AAAAAA"))
=> A value: AAAAAA
[:left 123]
[:right "hello"]
(match either
[:left num-val] (do-something-to-num num-val)
[:right str-val] (do-something-to-str str-val))