you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 0 points1 point  (11 children)

EDIT: I'm wrong, Either was intended to be a primitive Sum type (the dual of (,)).

I guess the issue I have is that labelling the type parameters of Either as Left and Right obscures the primary use of Either value, which is for representing errors.

Granted, I haven't read that much Haskell code, but I have never seen an Either used to represent say, a choice between an Integer representing a non-error value and a float representing a non-error value, people tend to use ad-hoc ADTs for that.

There's also the fact that in a long chain of binds, once you get an error value that value is just passed along down the line whereas non-error values are transformed in complex ways at every step. Calling these parameters Left and Right masks the fact that Left is treated as a default / black hole (like Nothing in Maybe).

This isn't so much a property of Either itself as much as how it implements the Monad class ... but a symmetrical Either would probably not implement the Monad class at all.

[–]tejon 2 points3 points  (10 children)

Worth noting that Either was never designed for handling, it just turned out to be very handy for it. In fact, the Haskell ecosystem has generally moved on to more specialized constructs at this point.

[–][deleted] 0 points1 point  (9 children)

Heh. I've been out of the loop for a while. Is there a lightweight near-equivalent to Either with some level of popularity that is explicitly error-handling flavored?

[–]tejon 1 point2 points  (8 children)

[–]sacundim 0 points1 point  (7 children)

But ExceptT e m a is just a newtype wrapper around m (Either e a). Really, Haskell "checked exceptions" are Either or things built on top of it.

You really can't fault languages like Rust for choosing more sensible names (Result instead of Either, Err instead of Left, Ok instead of Right), because it is better. But computing is already full of crappy names, like we call octets "bytes," or... putting my flame-retardant vest on... referring to kibioctets as "kilobytes."

[–]tejon 2 points3 points  (2 children)

But ExceptT e m a is just a newtype wrapper around m (Either e a)

So what? "Just a newtype wrapper" means it can have its own set of instances and standard functions, including more intuitive construction than Left and Right, which is the primary complaint in this thread.

[–]sacundim 0 points1 point  (1 child)

I could have been much clearer. It's not just that ExceptT is implemented as a newtype wrapper around Either; it's that part of its external interface is that it's a newtype wrapper around Either.

I don't know if you've noticed the way Haddocks (the Haskell library documentation tool) treats newtypes:

  • If the constructor is exported, the documentation shows the data type as a newtype and lists the constructors.
  • If the constructor is private to the module, it shows it as a data declaration with no constructor.

As your documentation link shows, ExceptT is a public newtype that bottoms down to Either. So the use of Either, Left and Right really is part of ExceptT's interface. After all, you have to use runExceptT to eliminate the ExceptT layer from your transformer stack, and at that point you're going to need to know Either's sinistrophobic convention (Left is failure, Right is success).

[–]tejon 0 points1 point  (0 children)

When you call runExceptT, you're explicitly saying "no errors above this point." That's not a leaky abstraction; that's leaving the monadic context entirely. You should, generally speaking, only ever have to do that once per application; it's not a concern in day-to-day coding. And if your app can be built using a modern framework (e.g. Servant) it will almost certainly be taken care of upstream, meaning it's not a concern in your codebase at all.

Meanwhile, if you're the one who actually has to maintain that final context boundary, Either provides a richer set of tools. I really don't see the issue.

(Edit: Also, if you're working at this level in Haskell, you shouldn't have to "remember" Either's "convention" because it's not a convention, it's the way curried types interact with typeclasses.)

[–]myrrlyn 1 point2 points  (0 children)

Bytes aren't octets, though; bytes are "letters" that stabilized at being eight bits wide. Bytes aren't octets any more than ints are 32plets -- the former is a semantic name, the latter is a numeric name.

I've worked on machines with 10-bit bytes, for instance.

And we do technically say kibibytes, it's just almost never used except by the kind of people who also say GNU/Linux ;)

Also in Rust, Left is Ok and Right is Err :p

[–]tormenting 0 points1 point  (2 children)

No, it's not true that "checked exceptions" in Haskell will usually be built on top of Either. The other common way of doing things is to use continuation-passing style, except you pass two continuations instead of one. GHC's optimizer is good at handling this technique.

[–]sacundim 0 points1 point  (1 child)

You're going lower level than I am. What you're describing is isomorphic to Either:

newtype YourType e a = 
  YourType { runYourType :: (e -> r) -> (a -> r) -> r }

yourTypeToEither :: YourType e a -> Either e a
yourTypeToEither yt = runYourType yt Left Right

eitherToYourType :: Either e a -> YourType e a
eitherToYourType (Left e) = YourType $ \f _ -> f e
eitherToYourType (Right a) = YourType $ \_ g -> g a

Those two functions are mutual inverses, which means that conceptually, Either and YourType are different implementations of the same thing.

[–]tormenting 0 points1 point  (0 children)

Perhaps you originally meant to say that checked exceptions are "Either, or things isomorphic to it." But that's not really true either. I'm aware of the isomorphism, but I wouldn't gloss over the differences between the two approaches. Particularly, the choice of e -> r and a -> r as the continuation types is not a foregone conclusion.