#PureScript is almost the perfect #Haskell replacement. Not only for the things it brings to the table, but also for the things it does *not*.

I mean actual *features* missing from PureScript and not gotchas like *no historical baggage*!

Some examples below. All of them are good for simplicity and newbie friendliness!

1. No laziness.

Laziness makes it harder to reason about performance. There are easy workarounds for most common usecases for laziness, and employing clever laziness tricks is usually not worth the cost of complicating the codebase.

Also, you can approximate thunks with functions when needed. Typeclass trickery even allows call-by-name evaluation (see github.com/natefaubion/purescr).

2. Manual recursion is discouraged.

PureScript's TCO only works in special cases. Normally you wouldn't even need to deal with this because existing implementations of functions like map, foldr etc. are already tail recursion safe. For the rare cases when you need to manually recurse, you can use `tailRec` and `tailRecM` to ensure stack safety.

3. No special syntax or support for linked lists. Strings are not simply lists of chars.

This could potentially be put in the "no historical baggage" category. But I feel that a lot of people, especially newbies, view the special treatment of lists and strings as a plus for Haskell.

However, the focus on lists causes massive confusion for newbies when they are encouraged to use types like Vector and Text in production instead. The special inbuilt syntax also is an active impediment for learning (for example, how do you refer to the higher kinded List type when deriving an instance? It's a mental leap to get to `[]` in `instance Foo [] where`).

4. No special syntax for Tuples.

Much of what was said for lists also applies to tuples. The special syntax is confusing to newbies and causes code complications. For example, each length of tuple is a separate type, and typeclasses in Haskell need to derive instances for each separately. They usually derive instances for tuples only up to length 7.

In addition, it's rarely needed. I am yet to find a usecase for tuples where PureScript's wonderful support for records doesn't suffice. I thought one such usecase was case matching on multiple values (`case (v1, v2) of ...`), but PureScript supports multi case match for this exact usecase and avoids unnecessary boxing (In PureScript you would write `case v1, v2 of ...`).

@haskman
Strict-by-default is just asking users do to what a compiler should do.

For the same reason we don't want to use manual memory management (even with the borrow checker assistance) *all the time*.

Let me focus on what's important and let the compiler find a best way to do it.

@dpwiz@qoto.orgThere are always tradeoffs. However, in my experience the complexity doesn't seem worth it in production code.

@dpwiz Code complexity. Code that exploits laziness is harder to understand and reason about.

@haskman I find the opposite is true. Is that complexity or difficulty an objective thing?

Follow

@haskman Either your claim about "laziness (by default) is bad" is objective/universal and one of is wrong (and I, personally, would like to stop being wrong). Or it is subjective and there's nothing for us to argue about.

@dpwiz Okay, assuming your question was genuine - Let me play the argument out on both sides for a bit -

A predictable order of evaluation, especially in the presence of bottom and infinity, makes it easier to understand the code. So in my mind it's objective yes.

However, I suspect you are thinking of something like this - anyone would find writing `1+2` clearer than the machine instructions needed to load and add those numbers, even though the order of doing things is less specified in the mathematical notation. So it's objective the other way in your mind.

So perhaps it's subjective because it depends on the level you are looking at things from, which might be different for different people.

@haskman Thank you. I now wonder how a proper qualifier would like like.

It can't be "laziness is bad if you need performance" as there are some good algorithms that leverage laziness to get amortized speed-ups.

It can't be "laziness is bad if you need to analyze code" as there are cases where order is irrelevant (e.g. commuting operations).

🤔

@haskman But anyway, I think that questioning laziness is barking up the wrong tree. What we should strive for is not strictness, but a better tooling that will answer the challenges of performance and legibility "in the large".

Otherwise we're end up in competition with the other more established languages and their outgrown ecosystems instead of walking on our strong foot.

@haskman needs to become a better Haskell, not a better Python, or a better Rust, or a better PureScript.

@dpwiz Laziness is not bad, it just trades off user friendliness. I personally like laziness, I like feeling smart when I tie the program up in knots and write seemingly impossible functional programs. But the fact is that code reviews in Haskell teams are harder than code reviews in PureScript teams. And it's an unnecessary cost because I'm yet to find a practical usecase where PureScript loses out to Haskell due to differing evaluation strategies.

@haskman Why do you even want to consider laziness at a review time? Without a profiler/benchmark data you can't point a finger at the expression and say "there's laziness in there, make it strict to go faster" on a hunch. It may as well go slower.

@haskman @dpwiz there is already a counterexample I provided: streams. In general, any structures you traverse from inside out are easier to write and understand with laziness. I thought that in another thread we agreed that there are no "natural" evaluation order. (As I side note: I think that cognitively laziness is easier because humans glance, not drill, but that's a topic for behavior scientists to figure out).

@jonn @dpwiz Can you be more specific? What algorithm are you thinking of that's easier to write and understand with laziness?

@haskman I'm talking about data strucutres, not algorithms. The data structure I'm talking about, namely, is "stream".

In Haskell it's just `Cons a (Stream a)` and you write code (algorithms) over it as normal. In Ocaml it's `type 'Cons of 'a * (unit -> 'a stream)`.

Same goes for zippers.

I'm not smart enough to know fancy algorithms, but if functions over possibly infinite datums, you can imagine how explicit thunking makes things worse.

github.com/lurk-lab/straume/bl all of this machinery can be avoided entirely with laziness, for example.

Sign in to participate in the conversation
Qoto Mastodon

QOTO: Question Others to Teach Ourselves
An inclusive, Academic Freedom, instance
All cultures welcome.
Hate speech and harassment strictly forbidden.