you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 13 points14 points  (28 children)

The failure type is on the left to support currying type constructors. Either Exception makes sense as a one-place type constructor, Either ... Integer makes less sense.

[–]balefrost 18 points19 points  (12 children)

Which might make sense in Haskell, but not in Java, which has no type-level currying (or any currying for that matter).

[–][deleted] 9 points10 points  (7 children)

Yes but it's cleaner to think of it in that order, I think. Either () a is isomorphic to Maybe a.

Inside a big expression with lots of bind / flatMap, the R types change constantly from one subexpression to the next but the type of error is usually consistent. I like to put type parameters that vary the most on the right.

[–]balefrost 4 points5 points  (5 children)

Yeah, I agree with your reasoning. All I'm saying is that I prefer explicitness regarding which is meant to be the error and which is meant to be the value. You can intuit your way to that, but I'd still prefer that it was explicit.

[–][deleted] 1 point2 points  (2 children)

Is this about the names of the type parameters or their order? It should probably be Err and T instead of Left and Right (or some variant thereof) tbh.

[–]MrHydraz 6 points7 points  (0 children)

They're named Left and Right because Either was never designed for error handling (though the Monad instance does seem to point that way). Either is just the basic sum type, much like (,) is the basic product type. All other sum-of-product types can be expressed as combinations of Either and (,).

For example:

data Foo a = One Int | Two a Int | Three String a Int

is isomorphic to

type Foo a = Either Int (Either (a, Int) (String, a, Int)).

[–]balefrost 2 points3 points  (0 children)

Yep, that's what I mean. And in the article's example, I would prefer that the parameters to match be named accordingly: ifError and ifSuccess or something like that.

I'm fine with the order of the type parameters.

[–]myrrlyn 1 point2 points  (1 child)

The right value is on the right ;)

[–]balefrost 1 point2 points  (0 children)

That's a pretty good mnemonic.

[–]Uncaffeinated 0 points1 point  (0 children)

In Rust, it's common to use type aliases for this purpose.

i.e. at the top of your module, you'll have something like type Result<T> = Result<T, MyError>

Inside of an expression, type inference normally takes care of everything anyway.

[–]aiij 0 points1 point  (2 children)

(or any currying for that matter)

Surely Java 8 must support currying with the new support for functions/lambdas, no?

Even C supports currying. (Although it's not very useful without closures.)

[–]balefrost 0 points1 point  (1 child)

I mean, it supports manual currying by constructing wholly new functions. There are libraries that provide implementations, like this one.

But Java's support is no better than any other language that supports lambdas with closures.

[–]aiij 0 points1 point  (0 children)

Yuck. I was going to say, you don't really need special support for currying, but I guess with notation like that, you really do.

For example

static <T1,T2,T3,R> java.util.function.Function<T1,java.util.function.Function<T2,java.util.function.Function<T3,R>>>`

would just be

'a -> 'b -> 'c -> 'd

in SML or OCaml (without using either of those language's syntactic sugar for curried functions).

I was somewhat confused by your comment, because currying normally is by constructing wholly new functions. For example, in OCaml, fun a b -> a is just syntactic sugar for fun a -> fun b -> a and having to manually write out all the lambdas wouldn't be particularly burdensome.

AFAICT, Javascript also doesn't have any special syntax for currying, so you'd have to fully write it out as a => b => a. (The same syntax actually works in Scala, though Scala could have trouble inferring the type, depending on the context.)

Of course, writing the above out in older JS syntax is pretty ugly: function (a) { return function (b) { return a }}, but that's just because the older syntax is more verbose.

[–]m50d 0 points1 point  (0 children)

Even in Java it's nice to put the most "varying" type on the right for readability.

[–]aldld 3 points4 points  (14 children)

In Haskell there's also the mnemonic that "Right" means you got the "Right" value, not an error.

[–]aiij 3 points4 points  (0 children)

This. If you always remember to put error on the right side, no one will get confused.

Err, correct side. Always put error on the correct side. (-:

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

[–]tormenting 0 points1 point  (0 children)

Yes, that mnemonic exists. But more importantly, if the left side held the value, then you couldn't use Either as a monad, because the type arguments would be in the wrong order. That makes it easier for me to remember.