News for hackers. Stuff that matters.
Re:From First Principles: Why Scala? by yw3410 on Thursday February 11, 2021 @11:42AM (#26103701)
I am continually perplexed at the swallowing of exceptions in certain FP communities.

Result types, Either et cetera make it extremely easy to swallow errors by flatMapping thoughtlessly losing the context of where they were - you typically /want/ the call stack when you hit into an exceptional flow.

Re:From First Principles: Why Scala? by dkarl on Thursday February 11, 2021 @01:06PM (#26104986)
As I am slowly acquiring the habits of thought that make it possible to read and write FP code at a reasonable speed, I'm horrified by how much idiomatic Scala FP code relies on projecting certain assumptions onto types and operations that seem, at first glance, to be neutral mathematical abstractions.

For example, I find myself hating the Either type, because I feel like there is a socially established convention that one half of the Either is the type that matters, the value that you want, the value that is the point of the computation you're doing, and the other half is a garbage value that should never be directly handled. So I really feel like I should conform to the convention and reserve Either for cases where one of the possible types doesn't matter. But how often is it true that one side of the Either doesn't matter? People want me to encode success/failure in an Either type, but if I do that, are they going to treat failure with the care it deserves?

I often handle Either (and Option) using pattern matching when I feel it's important to give both code paths equal importance and equal visibility in the code, but people change it because flatMap is supposedly more idiomatic, and they believe that eliminating pattern matching from their code is a sign of sophistication.

I feel like this stems from a strong desire among FP folks for the happy path to be the only one visible in the code, and the non-happy path to work by invisible magic. Maybe there are some brilliant programmers who achieve this by careful programming, but there are people mimicking them who seem to rely more on faith than logical analysis. They just flatMap their way through everything and trust that this results in correct behavior for the "less important" cases.

I'm sorry that this turned into a bit of a rant, but I'm entirely fed up with it, and it accounts for a lot of what I dislike about the code I work with on a daily basis.

Re:From First Principles: Why Scala? by clickok on Thursday February 11, 2021 @02:11PM (#26105794)
This was great, actually. I don't program in Scala, but it was very interesting to hear about the difference between types as abstractions vs types as they are used.

For unfamiliar topics or when presented with uncommon insight, I believe rants, monologues, even diatribes are actually some of the best things to read.

Re:From First Principles: Why Scala? by lmm on Thursday February 11, 2021 @08:50PM (#26109936)
> For example, I find myself hating the Either type, because I feel like there is a socially established convention that one half of the Either is the type that matters, the value that you want, the value that is the point of the computation you're doing, and the other half is a garbage value that should never be directly handled. So I really feel like I should conform to the convention and reserve Either for cases where one of the possible types doesn't matter. But how often is it true that one side of the Either doesn't matter? People want me to encode success/failure in an Either type, but if I do that, are they going to treat failure with the care it deserves?

There's always a tradeoff between making the happy path clear and making the error handling explicit. The whole point of Either is to be a middle ground between "both cases are equal weight and you handle them by pattern matching" (custom ADTs) and "only the happy path is visible, the error path is completely invisible magic" (exceptions). Given that people in Python or Java tend to use exceptions a lot more than they use datatypes, I'd argue that a typical Scala codebase puts more emphasis on actually handling errors than a typical codebase in other languages.

Where each case really is of equal weight, consider using a custom datatype (it's only a couple of lines: sealed trait A, case class B(...) extends A, case class C(...) extends A) rather than Either.

Re:From First Principles: Why Scala? by Fellshard on Thursday February 11, 2021 @02:04PM (#26105714)
I've been working through 'Haskell From First Principles', and it turned on a lightbulb: the 'right' half of Either is the 'important' one because its type variable is free to change.

  instance Functor (Either a) where    -- a is fixed here!
    fmap :: (b -> c) -> Either a b -> Either a c
    fmap _ (Left l)  = Left l
    fmap f (Right r) = Right (f r)
As a general rule, the last type parameter of a type carries special significance: the same applies to Tuples.

You _can_ trivially construct a type where the two labels are swapped; it's just labels, Left and Right aren't intrinsically important, except insofar as they reflect the positions of the type arguments in written text.

  data Either' a b = Left b | Right a
Re:From First Principles: Why Scala? by yw3410 on Thursday February 11, 2021 @04:16PM (#26107323)
This is also the reason we have the convention in Scala as well - the inference to partially apply the type works in a certain way. But I agree with the parent post, a more descriptive name would be better.
Re:From First Principles: Why Scala? by tomp on Saturday February 13, 2021 @06:44AM (#26123030)
The obvious (and obviousyl better/correct) name for that type is Result; instead, Either should be unbiased.
Re:From First Principles: Why Scala? by Fellshard on Thursday February 11, 2021 @06:44PM (#26109076)
Right; ostensibly you could create a language that lets you easily poke holes in any slot of a type, but I'm not sure you necessarily /gain/ a lot in doing so except for confusion. It would take a lot more convolution to specify types and instances for every function application.
Re:From First Principles: Why Scala? by rovolo on Thursday February 11, 2021 @02:20PM (#26105875)
> I often handle Either (and Option) using pattern matching when I feel it's important to give both code paths equal importance and equal visibility in the code, but people change it because flatMap is supposedly more idiomatic, and they believe that eliminating pattern matching from their code is a sign of sophistication.

Isn't this the same as letting exceptions bubble up in a non-FP language?

Re:From First Principles: Why Scala? by yw3410 on Thursday February 11, 2021 @04:26PM (#26107497)
Yes - but you /also/ lose the stack trace and it's /far/ too easy to do; hence my OP.

It is /infuriating/ to be knee-deep trying to work out which of your 30 Eithers failed with a non-descript error.

Re:From First Principles: Why Scala? by desbo on Thursday February 11, 2021 @05:01PM (#26107973)
You don't necessarily lose the stack trace. Typically the left side of an either is an Exception (or an error ADT that wraps one). When you want to handle the left case, you can log out the full trace as you would without Either.

The Monad instance for Either means that chaining them together with flatMap has a short-circuiting effect and the first failure will stop the rest of the chain from being evaluated. I find this actually makes it easier to know where your errors are happening, and also allows you to centralise your error handling logic.

Re:From First Principles: Why Scala? by yw3410 on Thursday February 11, 2021 @06:27PM (#26108928)
Sure - you can implicit srcloc or capture the Exception, both of which preserve it; but that's not the default behaviour and it's not what we recommend to beginners.

If you go onto the scaladoc for Either today, you see a stringly-typed Either where they discard the Exception.

Re:From First Principles: Why Scala? by kaba0 on Thursday February 11, 2021 @05:54PM (#26108550)
Hmm, when I first learnt Scala, I haven’t had too advanced FP knowledge, so I am yet to have first-hand experience with this sort of exception-handling and I’m yet to decide how good it is.

Compared to Haskell, it is probably better in some way because you have the proper stacktrace; but it “feels” impure a bit.. In a way Java’s exceptions are already an Either type with the result type and the thrown Exception (with “auto-decomposition”, unless checked exceptions) —- is the advantages like manual management of when mapping/flatmapping happens worth it in your opinion? Nonetheless thanks for the heads up, I might try out Scala again with the exception handling model you mentioned!

Re:From First Principles: Why Scala? by dkarl on Thursday February 11, 2021 @03:17PM (#26106556)
It's supposed to work like that, but it's a lot easier to screw up. Smart people screw it up all the time, and it's hard to spot in code review, whereas average programmers have no problem avoiding swallowing exceptions once they realize it's important, and if they do mess up it stands out like a sore thumb in the code.
Re:From First Principles: Why Scala? by jinfiesto on Thursday February 11, 2021 @12:50PM (#26104756)
You shouldn't swallow errors like that in FP either. You can trap the error in an effect type and throw it at the top level of your app when your effect type gets run after all of the code that processes that error value has a chance to recover. See something like Zio for an example, though you can do similar things without Zio.
Re:From First Principles: Why Scala? by randmeerkat on Thursday February 11, 2021 @01:26PM (#26105260)
If the team uses Zio, or maybe they’ve gotten lost in the religious war of Scalaz, vs Cats, vs Zio... One of the major issues I have with Scala is how fragmented the community is. Everyone has an opinion on how something should be done and everyone thinks that everyone else is wrong.
Re:From First Principles: Why Scala? by monksy on Friday February 12, 2021 @01:09PM (#26116593)
Well you have extremist who believe that you have to cull the community due to a few bad (but still contributing) apples. Additionally you have a big lightbending company that produces some questionable contributions to the community as if it's the "scala-endorsed way".

Don't get me started with the whole forcing of "lets be python like" initiatives as of late.

Re:From First Principles: Why Scala? by spion on Thursday February 11, 2021 @02:16PM (#26105850)
And will you get a call trace when you do that?
Re:From First Principles: Why Scala? by hderms on Thursday February 11, 2021 @04:23PM (#26107446)
we use error types that extend Exception specifically for the purpose of getting a call trace. A lot of people hate doing this but it's been extremely helpful on our project
Re:From First Principles: Why Scala? by yw3410 on Thursday February 11, 2021 @06:34PM (#26108977)
We do this too - but the other problem you get is that Exception aren't treated specially (like Any and Nothing) in the inference hierarchy so errors are liable to "collapse" to Throwable or Exception. I like wrapping them with a new type if possible to stop this behaviour.
Re:From First Principles: Why Scala? by Kototama on Thursday February 11, 2021 @12:10PM (#26104176)
It makes the code impure (that is with side-effects). One can also argue that if you can encode the potential errors in the type they are not exceptional.

It's not that bad though, because you get much less of these kind of errors than in a typical Java program (for example).

Re:From First Principles: Why Scala? by yw3410 on Thursday February 11, 2021 @12:31PM (#26104471)
Yes, it is a side-effect - and does make the code impure; but it's an /exceptional/ flow like OOM, infinite looping, stack overflows which also break equational reasoning.

My argument is that as a programmer you /choose/ the base lemmas which you're comfortable with - with an exception you're saying for a large swathe of your code, it will assume a lemma. When is /does/ break you get to point the finger at it with a stack trace saying here is where the lemma was broken. As an aside there are nicer ways to do this ala contracts, but exceptions aren't a /bad/ way.

The equivalent of try.toOption is try catch everything and discarding. The equivalent of flatMapping without adding surrounding context is equivalent to try catch rethrowing. Both of these are /much/ too common in FP codebases.

Re:From First Principles: Why Scala? by joshlemer on Thursday February 11, 2021 @01:16PM (#26105116)
This is spot on! Way too often in Scala codebases, which tend to wrap IO calls in Future or similar is that you'll have chains of side effecting futures like

    readFromDb()
      .flatMap(x => doSomethingElse(x))
      .flatMap(x => doSomethingElseAgain(x))
      .flatMap(x => onceAgain(x))
And this all gets passed up the call stack to one generic thing that basically does nothing in the case of error, maybe logging it and that's it. It's tempting to write code like this because it's so easy and looks so clean and readable, but in reality it is not how you build reliable software because what one must do in response to a failure from the first call is not the same as what one must do for a failure of the second call, or third or fourth, but this flatMap().flatMap().flatMap() style seduces the programmer into thinking they've handled errors when really they've just lumped the entire workflow as one big chain that can fail at any point and in practice usually just ignore all error cases.

One strategy I find does slightly mitigate this is to use future.transformWith[S](f: Try[T] => Future[S]). This at least presents to you the opportunity to think about error handling a bit more consciously, but I still find that the chaining operator approach nudges you away from thinking about error handling because it becomes quickly difficult to read.

It's probably to do with how limited the control structures are when dealing in Futures. You don't have while/if-else/etc, you must lift all your control into flatMaps and iterations are only doable via recursive calls with explicit accumulator state (yuck!) and nesting. So whereas a properly thoughtful treatment of error cases in synchronous code may take 20 or 30 lines of legible code, this gets tranformed in the async style to 20 or 30 levels of nested callbacks and recursive calls which becomes unreadable.

Re:From First Principles: Why Scala? by dkarl on Thursday February 11, 2021 @04:26PM (#26107492)
Amen. I saw exactly the same problem with over-reliance on flatMap, either explicitly or in for expressions, in future-heavy Akka code that I do in cats and cats-effect code.

People love the unifying abstractions underneath these types, and they love developing instincts about how to write code based on them, but from an application programming point of view, their instincts are often counterproductive. I don't think people take a mathematical enough viewpoint. The abstractions can't tell you what is important and unimportant. They can't tell you how your code should be shaped. They can't tell you which values should be transformed further and which should be short-circuited. They can't tell you which values deserve to be given a name for readability and which values should be anonymous.

"All these values are monads, so I can combine them with a for expression" is a meaningless statement of a trivial mathematical fact, not a clue about how you should write your code.

I think error handling code can be written in a straightforward style, but it isn't as pretty as people would like. I think the trap they fall into is holding onto elegance while they reach for correctness, instead of holding fast to correctness while they reach for elegance.

Re:From First Principles: Why Scala? by joshlemer on Thursday February 11, 2021 @06:12PM (#26108781)
That's a great observation about for-comprehensions. It's an other one of these cute niceties that in the most basic cases can come in handy but again nudges users away from consciously thinking through what they're doing (good luck handling the error path in a for-comprehension over Futures).
Re:From First Principles: Why Scala? by staticautomatic on Friday February 12, 2021 @12:07AM (#26110929)
I dunno. You see the same pattern with chained class methods in Python and it suffers from the same exception specificity problems, but doing it is still a fair judgment call. Sometimes all you need to know is that there was an error in the chain. Like if an exception is raised by dataframe.transpose().to_dict().values() I don’t ask myself where in the chain the error occurred, I just think, “oh that’s weird why couldn’t I turn that into a list of dicts? Did I call those methods on the wrong object?”
Re:From First Principles: Why Scala? by joshlemer on Friday February 12, 2021 @01:05AM (#26111146)
Your point's well taken. I'd say that there is a bigger issue in the case of flatMapping side-effectful monads because they're typically dealing with things like writing/reading from databases and the error cases should be thoughtfully considered (do I have to roll something back or perform compensating actions, or clean up anything?)
Re:From First Principles: Why Scala? by staticautomatic on Friday February 12, 2021 @03:59PM (#26118478)
If understood “flatmapping monads” as anything other than the name of a band I’m sure I would agree.
Re:From First Principles: Why Scala? by mhitza on Thursday February 11, 2021 @02:03PM (#26105708)
Doesn't Scala have transformer libraries like ExceptT in Haskell. Aside, much of what this comment thread is about, is similar in Haskell-land as well.

Anyway, a pattern I've started to use in Haskell with ExceptT is along the lines of

    handledIo = runExceptT $ do
      failable <- someIo
      can_handle <- anotherIo failable `catchError` handlerOfSubsetOfExceptionsOrRethrows
      return $ updateState can_handle
So the code still remains legible (minus the plethora of Haskell operators, data wrappers and unwrappers :)), I can trap individual exceptions along the way, which I can handle. And at the end unhandled exceptions are returned by the function as Left.
Re:From First Principles: Why Scala? by ud_visa on Friday February 12, 2021 @04:27AM (#26111928)
How is it different from

  try {
    doSomethingElse(x)
    doSomethingElseAgain(x)
    onceAgain(x)
  } catch {
    ...
  }
where each method just returns Unit or throws exception? The version with IO/Future is superior since it at least explicitly states that you can get an error here. If you want to say that Go-style error handling is better because if forces errors handling, I can kind of buy it, but it also has some cost.

>You don't have while/if-else/etc

Maybe you are looking for ifM/whileM functions from e.g. here: typelevel.org/cats/api/cats/Monad.html.

Re:From First Principles: Why Scala? by lmm on Thursday February 11, 2021 @08:42PM (#26109889)
The call stack is "magic" built into the language, so as soon as you're not using the "blessed" way of error handling, you lose it and need to rebuild the same functionality "by hand".

I agree that some kind of logical call stack is a very useful thing to have, and I'd recommend implementing something along the lines of https://github.com/lancewalton/treelog that provides it.

Re:From First Principles: Why Scala? by AzzieElbab on Thursday February 11, 2021 @08:26PM (#26109813)
How do you mean? If your either turned left you should be stuck with that exact left and no amount of flatmapping should effect your stack, it simply does not kick in unless you added something like leftmap that swallows the exception
Re:From First Principles: Why Scala? by spion on Thursday February 11, 2021 @02:15PM (#26105829)
I think the solution is to have typed exception modelled as polymorphic variants. Something similar to Swift.