types - 在 Clojure 中表示 sum 类型(a b)的惯用方式

标签 types clojure discriminated-union

已编辑。我现在的问题是:在静态类型语言中,通常使用什么惯用的 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 or Either[+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被认为是不好的做法
    数量的原因。

    但是,我们可以在运行时做得很好。
  • 我想确保的是 :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 能做到的事情:在运行时操作类型。
  • 使用 map 来表示值。
  • 高阶编程:将 fns 的映射传递给另一个函数。

  • 如果您正在使用 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/

    相关文章:

    .net - 打印受歧视联盟的案例标识符

    typescript :如何解释扩展和函数类型之间的这种交互

    python - 将参数从 Python 传递到 C 时出现问题

    c++ - 如何将字符串转换为 uint32_t

    scala - 如果函数可以是不同的类型,单子(monad)规则将如何应用

    variables - 是否可以修改 let 绑定(bind)?

    c# - 如何在 F# 中创建值类型的联合类型?

    reflection - F# 可区分联合的拆箱值

    c++ - 省略数据类型(例如 "unsigned"而不是 "unsigned int")

    clojure - 如何在 Clojure 中进行钩子(Hook)