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

Set*_*sue 13 scala either

我知道现在很难在不破坏现有代码的情况下进行更改,但我想知道为什么首先要这样做。

为什么不只是:

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]
Run Code Online (Sandbox Code Playgroud)

这里是否有一些我没有看到的缺点......?

lef*_*out 11

不确定这个答案与 Scala 到底有多相关,但它肯定是在 Haskell 中,这显然是 Scala 的Either借用处,所以这可能是 Scala 这样做的最佳历史原因。

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

  • 方式 EitherA,B ? A ? B
  • 两个共同投影和LeftA,B : A -> A?BRightA,B : B -> A?B
  • 这样对于任何类型Y和任何函数and ,都只存在一个具有and属性的函数。fA : A -> YfB : B -> Yf : A?B -> YfA = f ? LeftA,BfB = f ? RightA,B

为了以数学方式表达这一点,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
Run Code Online (Sandbox Code Playgroud)

显然,在 Scala 中,Haskell 只是将未指定的第二个参数left42作为类型变量保留(除非启用了单态限制),因此您以后可以在任何需要 some Either Double Rfor any type 的上下文中使用它R。当然也可以明确表示

right2 :: Either a Int
right2 = Right 2

left42 :: Either Double a
left42 = Left 42

main :: IO ()
main = print $ (+) <$> right2 <*> left42
Run Code Online (Sandbox Code Playgroud)

这在 Scala 中当然也是可能的。

  • 鉴于 `scala.Either` 的原作者也是 Scalaz 的原作者,这可能是对标题中“为什么”问题的一个很好的答案。 (4认同)

Rex*_*err 10

我发现您的计划没有任何有意义的缺点。在过去八年左右的时间里,我使用了我自己的变体,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)
}
Run Code Online (Sandbox Code Playgroud)
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!
}
Run Code Online (Sandbox Code Playgroud)

但是,我从不这样做。我可以o用来获得正确的类型。所以没关系!其他一切都更容易(例如模式匹配和更改一种情况而不是另一种情况……除了切换无人居住的分支的类型之外,您不需要无缘无故地case Left(l) => Left(l)重建它Left)。

还有其他情况(例如提前设置类型)看起来应该很重要,但实际上几乎不可能解决(例如,因为协方差无论如何都会找到共同的超类型,所以您设置的内容不会限制任何东西)。

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

  • Tysvm 的答案,雷克斯!看到如此明确地梳理出细微差别是非常令人满意的。它还可以在未来过渡到更有效的模式,即使现在很难做到这一点。我们经历了从非右派到右派的转变。似乎可以启用类似的转换来将 Either 转换为与您的 Ok 解决方案相同的类型形状。 (2认同)