Why do `Left` and `Right` have two type parameters?

谁说胖子不能爱 提交于 2020-12-01 03:23:09

问题


I understand it would be difficult to change now without breaking existing code, but I'm wondering why it was done that way in the first place.

Why not just:

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]

Is there some drawback here that I'm failing to see...?


回答1:


Not sure how relevant this answer really is to Scala, but it certainly is in Haskell which is evidently where Scala's Either was borrowed from and so that's probably the best historical reason for why Scala did it this way.

Either is the canonical coproduct, i.e. for any types A and B you have

  • The type EitherA,B ≈ A ⊕ B
  • Two coprojections LeftA,B : A -> A⊕B and RightA,B : B -> A⊕B
  • such that for any type Y and any functions fA : A -> Y and fB : B -> Y, there exists exactly one function f : A⊕B -> Y with the property that fA = f ∘ LeftA,B and fB = f ∘ RightA,B.

To formulate this mathematically, it is quite helpful to have the information which particular Left you're working with explicit, because else the domains of the morphisms would be all unclear. In Scala this may be unnecessary because of implicit covariant conversion, but not in maths and not in Haskell.

In Haskell it isn't really an issue at all, because type inference will automatically do what's needed:

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

Unlike, apparently, in Scala, Haskell just leaves the unspecified second argument of left42 as a type variable (unless the monomorphism restriction is enabled), so you can later use it in any context requiring some Either Double R for any type R. Of course it's possible to make that explicit too

right2 :: Either a Int
right2 = Right 2

left42 :: Either Double a
left42 = Left 42

main :: IO ()
main = print $ (+) <$> right2 <*> left42

which surely is possible in Scala just as well.




回答2:


There's no meaningful drawback that I've found to your scheme. For the last eight years or so I've used my own variant of Either which is exactly as you describe under another name (Ok[+Y, +N] with Yes[+Y] and No[+N] as the alternatives). (Historical note: I started when Either was not right-biased, and wanted something that was; but then I kept using my version because it was more convenient to have only half the types.)

The only case I've ever found where it matters is when you pattern match out one branch and no longer have access to the type information of the other branch.

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!
}

However, I never do this. I could just use o to get the right type. So it doesn't matter! Everything else is easier (like pattern matching and changing one case and not the other...you don't need case Left(l) => Left(l) which rebuilds the Left for no reason except to switch the type of the uninhabited branch).

There are other cases (e.g. setting types in advance) that seem like they should be important, but in practice are almost impossible to make matter (e.g. because covariance will find the common supertype anyway, so what you set doesn't constrain anything).

So I think the decision was made before there was enough experience with the two ways to do it, and the wrong choice was made. (It's not a very wrong choice; Either is still decent.)



来源:https://stackoverflow.com/questions/64656786/why-do-left-and-right-have-two-type-parameters

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!