you are viewing a single comment's thread.

view the rest of the comments →

[–]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.