【问题标题】:Why do `Left` and `Right` have two type parameters?为什么 `Left` 和 `Right` 有两个类型参数?
【发布时间】:2020-11-03 22:49:45
【问题描述】:

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

为什么不只是:

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]

这里有什么我没看到的缺点吗...?

【问题讨论】:

  • 请注意,有人在这里问过关于 Try 的相同问题,但从未得到满意的答案:stackoverflow.com/questions/22904250/…
  • 贡献者频道上的相关discussion。目前尚不清楚为什么最初的设计是这样的,但似乎没有进行更改,因为它可能会破坏代码(还不错),但也会破坏交叉编译(真的很糟糕!)当然可以使用scala-collection-compat库之类的东西来解决,但当时似乎不是这样。

标签: scala either


【解决方案1】:

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

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

  • 类型Either<sub>A,B</sub> ≈ A ⊕ B
  • Left<sub>A,B</sub> : A -&gt; A⊕BRight<sub>A,B</sub> : B -&gt; A⊕B 的两个共投影
  • 因此对于任何类型Y 和任何函数f<sub>A</sub> : A -&gt; Yf<sub>B</sub> : B -&gt; Y,只存在一个函数f : A⊕B -&gt; Y,其属性为f<sub>A</sub> = f ∘ Left<sub>A,B</sub>f<sub>B</sub> = f ∘ Right<sub>A,B</sub>

要以数学方式表述这一点,明确您正在使用的特定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 中当然也是可能的。

【讨论】:

  • 鉴于scala.Either的原作者也是Scalaz的原作者,这可能是对标题中“为什么”问题的一个很好的回答。
【解决方案2】:

我发现你的方案没有任何有意义的缺点。在过去八年左右的时间里,我一直使用我自己的 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) =&gt; Left(l),它会无缘无故地重建Left,除了切换无人分支的类型)。

还有其他情况(例如预先设置类型),似乎它们应该很重要,但实际上几乎不可能成为问题(例如,因为协方差无论如何都会找到共同的超类型,所以您设置的内容不会限制任何内容)。

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

【讨论】:

  • Tysvm 这个答案,雷克斯!看到如此明确地挑逗出细微差别是非常令人满意的。它还可以在未来过渡到更有效的模式,即使现在很难做到。我们经历了从非右倾的“要么”到“右倾”的转变。似乎可以启用类似的转换以将 Either 转换为与您的 Ok 解决方案相同的类型形状。
  • 谢谢,雷克斯。我只能接受一个答案,但这个答案也很有价值。
猜你喜欢
  • 1970-01-01
  • 2017-11-24
  • 2022-01-24
  • 2019-01-17
  • 1970-01-01
  • 2019-08-18
  • 2018-04-01
  • 2017-01-31
  • 2019-06-09
相关资源
最近更新 更多