Hacker News new | ask | show | jobs
by runeblaze 690 days ago
I second the conclusion as (a brutal conclusion, but still) to stop using Haskell. Haskell allows imperative-like code but the ergonomics for day-to-day big-tech engineering is far from good. The state monad or lens are excellent tools to re-create a controlled imperative language in a vacuum, and is frankly impressive how much mutation we can conjure up from purity, but the error messages or the required understanding of PLT-ish things makes it non-scalable to "real" teams.
1 comments

Haskell almost seems like it was intentionally designed to perform poorly on real computers, primarily because of space leaks and secondarily because the non-strict evaluation gets compiled into a lot of function pointer jumps, which branch predictors hate.

I think it's funny that they make you write linked list code as a metaphor for generators, but it seems like it should be the other way round.

(Also, it has exceptions which are a bad language feature, and typed throws which are a worse one.)

It seems that way because it kind of is. The early days of functional research were equally focused on designing alternative computer architectures that were more suited to functional paradigms.

Now that hardware angle has not been very successful on the whole, and we are left with languages that end up feeling a bit out of place on the hardware we have today.

Another thing to note is that there is a lot of untapped potential in fb compilers. It’s suffering from underinvestment.

> (Also, it has exceptions which are a bad language feature, and typed throws which are a worse one.)

Can you elaborate? You can't stop people simulating exceptions with sum types, and if you have exceptions, why wouldn't you want them to be typed?

By simulating exceptions, do you mean a `Result t e` type (which Haskell calls `Either l r`)? You can use these and the Functor/Applicative/Monad hierarchy to handle errors.

What is presumably talked about is that Haskell also has actual exceptions, generated by calling e.g. `error` or `undefined`.

The semantics of these are.. interesting, mostly thanks to lazy evaluation. For example, `fst (5, error "second") ` is safe to evaluate because the second half of the tuple is a thunk and does not get evaluated. Additionally, there is, to my knowledge, no way to handle exceptions in pure code, presumably due to the undefined evaluation order.

That said, I'm not sure what the alternative would be, because a function like !! (list indexing) can fail and dealing with its fallibility would be a big burden on the programmer.

> By simulating exceptions, do you mean a `Result t e` type (which Haskell calls `Either l r`)?

Yes, exactly.

> The semantics of these are.. interesting, mostly thanks to lazy evaluation. For example, `fst (5, error "second") ` is safe to evaluate because the second half of the tuple is a thunk and does not get evaluated

Correct, and the semantics of loops is also.. interesting. For example `fst (5, last [1..])` is also safe to evaluate.

> What is presumably talked about is that Haskell also has actual exceptions, generated by calling e.g. `error` or `undefined`.

Well, I'm not sure, that's why I asked. I'm trying to understand what astrange meant by "it has exceptions which are a bad language feature, and typed throws which are a worse one". (Throwing exceptions from pure code should be left to such cases, that are impossible to recover from, in my opinion.)

> there is, to my knowledge, no way to handle exceptions in pure code, presumably due to the undefined evaluation order

Correct

> That said, I'm not sure what the alternative would be, because a function like !! (list indexing) can fail and dealing with its fallibility would be a big burden on the programmer.

Indeed. Even more so, what is one supposed to do when an invariant has been violated due to a programming error and there's no way to make progress? Haskell's exceptions are essential. It's even better when they're used in a well typed, well scoped manner, such as provided by my effect library Bluefin

https://hackage.haskell.org/package/bluefin-0.0.6.0/docs/Blu...

That's why I wanted to understand more about what astrange meant. It doesn't match my understanding!

Hm yeah, I'm not sure what astrange's point was, other than probably dissatisfaction with the combination of exceptions and Either (which I think is awfully named, as it implies a certain 'equality' between l and r).

I haven't used Bluefin, but don't Bluefin exceptions suffer from the same issue where it, essentially, 'infects' the program flow? For example, `fst (5, error "second")` seems like it would translate to `fst $ (5,) <$> throw e "second"`, where you would also need to pull an `e` from somewhere. More realistic, something like head would probably be quite awkward:

head e [] = throw e "head: empty list" head e (x:xs) = pure x

You now need to pass in the exception handle, and the return value is now `Eff es a` instead of `a`. This means that you cannot just use the result, so you will likely need to fill your program with monadic stuff like do-notation, <$> and <$>, complicating the program flow and likely also reducing laziness.

You're right, but my response would be that

    fst (5, error "second")
and

    head [] = error "empty list"
    head (x:xs) = x
are simply not things you should be writing. They're not good program design and it's not a weakness that Bluefin doesn't support them unaltered.
Sum types aren't a simulation of exceptions.
Can you elaborate?
Sure, exceptions are very different to Result-style error handling - even checked exceptions. Here are some differences:

* Errors must be explicitly listed as part of the function signature. Checked exceptions and the equivalent for exceptions but they are rarely used in practice. I think Android uses them, but they were so unpopular in C++ that they removed them from the language!

* The syntax to catch and handle errors is very different and more more verbose for exceptions. It can also make flow control a real pain in some languages where you can't declare a variable outside the try body (e.g. references in C++).

* Result errors need to be explicitly handled whereas exceptions are silently propagated by default.

Even though they're similar enough that you could translate one to the other in most cases, they're different enough that saying one is "an emulation" of the other is just stupid.

In my experience Result-based handling is far superior with two exceptions:

1. In functional code like map & filter where it can become quite awkward to explicitly deal with returning errors.

2. It's hard to get a stack trace from where the Err was created rather than from where it was unwrapped. Less of a problem with exceptions which record a stack trace from where they were thrown (in most languages anyway - C++ is an annoying exception).

> saying one is "an emulation" of the other is just stupid.

I did say that indeed. Am I to conclude doing so was stupid? If so I would find that very rude.

(For what it's worth I was trying to understand what astrange meant by "it has exceptions which are a bad language feature, and typed throws which are a worse one" and offering that characterization as a way of trying to tease out exactly what he/she meant. Your response contains many interesting points and I would otherwise be interested in discussing with you further, but I'm not too inclined to now that you have suggested you might think I'm stupid.)

They don't get stack traces, for one. (That's arguably the biggest problem with Rust: .unwrap() gives you a stack trace, but has problems; whereas ? erases your stack trace.)

In principle, static analysis could identify unhandled exceptions, then trace the exception, then make that information available to the top-level "Err returned from main" handler. In practice, that's never going to happen in Rust.

Sum types can have stack traces by adding a stack trace on creation. `Result<(), &'static str>` does not have a stack trace, but `Result<(), std::backtrace::Backtrace>` sure does. YMMV as to whether it makes sense to do that in any particular circumstance.
Sure, if your definition of exceptions includes "must include a stack trace", then sum types can't simulate exceptions. But by that definition Haskell hasn't had exceptions until the last year or two. You might agree with that (I don't) but I'm trying to understand astrange, who said "[Haskell] has exceptions which are a bad language feature, and typed throws which are a worse one". It seems doubtful that "having a stack trace" is part of what he/she considers bad about exceptions, so that aspect is probably not relevant to my line of questioning. What exactly is bad about exceptions? That's the point of me forking off this thread. So far no one has offered an answer.