为什么"Left"和"Right"有两个类型参数



我知道如果不破坏现有代码,现在很难进行更改,但我想知道为什么一开始就这样做。

为什么不只是:

sealed trait Either[+A, +B]
case class Left[A](x: A) extends Either[A, Nothing]
case class Right[B](x: B) extends Either[Nothing, B]

这里有什么我看不到的缺点吗。。。?

不确定这个答案与Scala的相关性有多大,但肯定是在Haskell中,这显然是Scala的Either的借鉴之处,所以这可能是Scala这样做的最好历史原因。

Either是规范的副产品,即对于任何类型的AB,都有

  • 类型EitherA,B≈ A ⊕ B
  • 两个共投影LeftA,B: A -> A⊕BRightA,B: B -> A⊕B
  • 使得对于任何类型的Y以及任何函数fA: A -> YfB: B -> Y恰好存在一个具有fA= f ∘ LeftA,BfB= f ∘ RightA,B性质的函数f : A⊕B -> Y

要用数学公式表达这一点,明确你正在处理的特定Left的信息是很有帮助的,因为否则态射的域都不清楚。在Scala中,由于隐式协变转换,这可能是不必要的,但在数学和Haskell中都没有。

在Haskell中,这根本不是一个问题,因为类型推理会自动完成所需的操作:

GHCi, version 8.6.5: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /tmp/haskell-stack-ghci/2a3bbd58/ghci-script
Prelude> let right2 = Right 2
Prelude> let left42 = Left 42.0
Prelude> (+) <$> right2 <*> left42
Left 42.0

显然,与Scala不同的是,Haskell只是将left42的第二个未指定参数保留为类型变量(除非启用了单态性限制(,因此您以后可以在任何类型R需要Either Double R的上下文中使用它。当然,也有可能将其明确化为

right2 :: Either a Int
right2 = Right 2
left42 :: Either Double a
left42 = Left 42
main :: IO ()
main = print $ (+) <$> right2 <*> left42

这在Scala中也是可能的。

我发现你的方案没有任何有意义的缺点。在过去的八年左右的时间里,我使用了我自己的Either变体,这与你在另一个名字下描述的完全一样(Ok[+Y, +N]Yes[+Y]No[+N]作为替代品(。(历史笔记:我从Either没有正确偏见的时候开始,想要一些正确的东西;但后来我一直使用我的版本,因为只有一半的类型更方便。(

我发现的唯一重要的情况是,当您对一个分支进行模式匹配,而不再能够访问另一个分支的类型信息时。

def foo[A, B: Typeclass](e: Either[A, B]) =
implicitly[Typeclass[B]].whatever()
// This works
myEither match {
case l: Left[L, R]  => foo(l)
case r: Right[L, R] => foo(r)
}
def bar[N, Y: Typeclass](o: Ok[N, Y]) =
implicitly[Typeclass[Y]].whatever()
// This doesn't work
myOk match {
case y: Yes[Y] => bar(y)  // This is fine
case n: No[N]  => bar(n)  // Y == Nothing!
}

然而,我从来没有这样做过。我可以使用o来获得正确的类型。所以没关系!其他一切都更容易(比如模式匹配和更改一种情况而不是另一种情况……你不需要case Left(l) => Left(l),它会毫无理由地重建Left,只是为了切换无人居住分支的类型(。

在其他情况下(例如,提前设置类型(,似乎很重要,但在实践中几乎不可能发挥作用(例如,因为协方差无论如何都会找到常见的超类型,所以你设置的内容不会约束任何东西(。

所以我认为这个决定是在对这两种方法有足够的经验之前做出的,而且做出了错误的选择。(这不是一个非常错误的选择;Either仍然不错。(

最新更新