Types Clojure中表示和类型(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)上的条件所污

编辑。我现在的问题是:在静态类型语言中,通常使用什么惯用Clojure构造来代替sum类型?到目前为止的共识:如果行为可以统一,则使用协议,否则使用标记对/映射,在前置和后置条件中放置必要的断言

Clojure提供了许多表示产品类型的方法:向量、映射、记录……但如何表示,也称为标记的联合和变体记录?类似于Haskell中的a b或Scala中的[+a,+b]

我想到的第一件事是一个带有特殊标记的地图:
{: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特殊情况-
    nil
    值在不同的地方是特殊情况,可以用作“Maybe”的“None”部分。例如,
    if let
    非常有用

  • 模式匹配-base clojure除了对序列进行分解外,对这方面没有太多的支持,但是有各种库可以做到这一点。请参阅[更新:在评论中,mnicky说这已经过时,您应该使用]

  • 按类型使用OO-按类型选择方法。因此,您可以返回父类的不同子类,并调用重载的方法来执行所需的不同操作。如果你来自一个功能性背景,你会觉得很奇怪/笨拙,但这是一个选择

  • 手动标记-最后,您可以使用带有显式标记的
    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))