为什么 `Left` 和 `Right` 有两个类型参数?
Why do `Left` and `Right` have two type parameters?
我知道现在很难在不破坏现有代码的情况下进行更改,但我想知道为什么首先要这样做。
为什么不只是:
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]
这里有什么我没有看到的缺点吗...?
我发现您的方案没有明显的缺陷。在过去八年左右的时间里,我使用了自己的 Either
变体,这与您在另一个名称下所描述的完全一样(Ok[+Y, +N]
与 Yes[+Y]
和 No[+N]
作为替代品). (历史记录:当 Either
不是 right-biased 时,我就开始了,我想要的是;但后来我继续使用我的版本,因为只有一半的类型更方便。)
我发现唯一重要的情况是当您对一个分支进行模式匹配并且不再能够访问另一个分支的类型信息时。
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
还是不错的。)
不确定这个答案与 Scala 的相关性如何,但它肯定在 Haskell 中,这显然是 Scala 的 Either
被借用的地方,所以这可能是 Scala 的最佳历史原因这样做的。
Either
是规范的 coproduct,即对于任何类型 A
和 B
你有
- 类型
Either<sub>A,B</sub> ≈ A ⊕ B
- 两个共投影
Left<sub>A,B</sub> : A -> A⊕B
和 Right<sub> A,B</sub> : B -> A⊕B
- 这样对于任何类型
Y
和任何函数 f<sub>A</sub> : A -> Y
和 f<sub>B</sub> : B -> Y
, 恰好存在一个函数 f : A⊕B -> Y
与 属性 即 f<sub>A</sub> = f ∘ 左<sub>A,B</sub>
and f<sub>B</sub> = f ∘ 右<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
的未指定第二个参数保留为 类型变量 (除非启用了单态限制),因此您以后可以在任何需要 Either Double R
的任何类型 R
的上下文中使用它。当然也可以将其明确化
right2 :: Either a Int
right2 = Right 2
left42 :: Either Double a
left42 = Left 42
main :: IO ()
main = print $ (+) <$> right2 <*> left42
这在 Scala 中当然也是可能的。
我知道现在很难在不破坏现有代码的情况下进行更改,但我想知道为什么首先要这样做。
为什么不只是:
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]
这里有什么我没有看到的缺点吗...?
我发现您的方案没有明显的缺陷。在过去八年左右的时间里,我使用了自己的 Either
变体,这与您在另一个名称下所描述的完全一样(Ok[+Y, +N]
与 Yes[+Y]
和 No[+N]
作为替代品). (历史记录:当 Either
不是 right-biased 时,我就开始了,我想要的是;但后来我继续使用我的版本,因为只有一半的类型更方便。)
我发现唯一重要的情况是当您对一个分支进行模式匹配并且不再能够访问另一个分支的类型信息时。
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
还是不错的。)
不确定这个答案与 Scala 的相关性如何,但它肯定在 Haskell 中,这显然是 Scala 的 Either
被借用的地方,所以这可能是 Scala 的最佳历史原因这样做的。
Either
是规范的 coproduct,即对于任何类型 A
和 B
你有
- 类型
Either<sub>A,B</sub> ≈ A ⊕ B
- 两个共投影
Left<sub>A,B</sub> : A -> A⊕B
和Right<sub> A,B</sub> : B -> A⊕B
- 这样对于任何类型
Y
和任何函数f<sub>A</sub> : A -> Y
和f<sub>B</sub> : B -> Y
, 恰好存在一个函数f : A⊕B -> Y
与 属性 即f<sub>A</sub> = f ∘ 左<sub>A,B</sub>
andf<sub>B</sub> = f ∘ 右<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
的未指定第二个参数保留为 类型变量 (除非启用了单态限制),因此您以后可以在任何需要 Either Double R
的任何类型 R
的上下文中使用它。当然也可以将其明确化
right2 :: Either a Int
right2 = Right 2
left42 :: Either Double a
left42 = Left 42
main :: IO ()
main = print $ (+) <$> right2 <*> left42
这在 Scala 中当然也是可能的。