是否存在类似Either的标准和类型,但有3种情况? Haskell有These
,但事实并非如此。
答案 0 :(得分:16)
我认为严重依赖这样的类型是一种反模式。
使用代数数据类型得到的最好的一点是,结果类型会告诉您有关您正在使用的域的信息。对于像Choice<T1, T2, T3>
这样的通用类型,您实际上并没有对域名进行任何说明。
我认为option<T>
(又名Maybe
)非常明确,因为它表示T
类型的值存在或由于某种原因而丢失。我认为Either<'T, exn>
仍然很明确,因为它说你得到了一个值或一个例外。但是,当您遇到两个以上的案例时,很难理解案例的含义,因此明确定义名称与域匹配的类型可能是一个好主意。
(我偶尔在F#中使用Choice<T1, T2, T3>
,但用法通常仅限于小范围 - 少于50行代码 - 这样我就可以轻松找到代码周围的含义消耗它。)
答案 1 :(得分:8)
在最近的Haskell中,我打开了一些厨房水槽。
{-# LANGUAGE PolyKinds, DataKinds, GADTs, KindSignatures,
TypeOperators, PatternSynonyms #-}
然后我定义了类型级列表成员资格
data (:>) :: [x] -> x -> * where
Ze :: (x ': xs) :> x
Su :: xs :> x -> (y ': xs) :> x
现在我拥有了所有有限的总和,而没有找出一大堆OneOfN类型定义:
data Sum :: [*] -> * where
(:-) :: xs :> x -> x -> Sum xs
但是,为了解决Tomas关于可读性的问题,我会使用模式同义词。事实上,这种事情是我多年来一直在关注模式同义词的原因。
你可以有一个有趣的Maybe
版本:
type MAYBE x = Sum '[(), x]
pattern NOTHING :: MAYBE x
pattern NOTHING = Ze :- ()
pattern JUST :: x -> MAYBE x
pattern JUST x = Su Ze :- x
您甚至可以使用newtype
来构建递归总和。
newtype Tm x = Tm (Sum '[x, (Tm x, Tm x), Tm (Maybe x)])
pattern VAR :: x -> Tm x
pattern VAR x = Tm (Ze :- x)
pattern APP :: Tm x -> Tm x -> Tm x
pattern APP f s = Tm (Su Ze :- (f, s))
pattern LAM :: Tm (Maybe x) -> Tm x
pattern LAM b = Tm (Su (Su Ze) :- b)
newtype包装器还允许您对以这种方式构建的类型进行instance
声明。
当然,您也可以使用模式同义词来很好地隐藏迭代的Either
。
这种技术并不仅限于总和:你也可以为产品做到这一点,而这正是de Vries和Löh的Generics-SOP库中发生的事情。
这种编码的最大胜利是数据的描述本身(类型级别)数据,允许您在不破坏编译器的情况下烹饪大量deriving
样式的功能。
将来(如果我有办法),所有数据类型都将定义,而不是声明,数据类型描述由指定代数结构的数据组成(允许数据及其外观的通用设备(因此,您可以看到在处理特定类型时您正在做什么)。
但未来已经在这里了。
答案 2 :(得分:3)
您使用的是哪种语言?如果它是F#,则有一个三向Choice<'T1,'T2,'T3>
type。 (除了更“标准”的双向类型外,还有4,5,6和7路选择类型。)
答案 3 :(得分:3)
对于scala,来自Scalaz的Either3
:https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/Either3.scala
答案 4 :(得分:3)
这些被称为联合产品真的Either
只是一个2参数联合产品。您可以使用无形库中的帮助程序使用以下方法构建任意长度的联合产品:
type CP = Int :+: String :+: Boolean :+: CNil
val example = Coproduct[CP]("foo")
然后,您可以使用所有有趣的poly
魔法来映射它们或执行其他操作:
object printer extends Poly1 {
implicit def caseInt = at[Int](i => i -> s"$i is an int")
implicit def caseString = at[String](s => s -> s"$s is a string")
implicit def caseBoolean = at[Boolean](b => s -> s"$b is a bool")
}
val mapped = example map printer
mapped.select[(String, String)] shouldEqual "foo is a string"
就我所知,Scala.JS + Shapeless可以一起工作,所以这可能会给你你想要的东西。