Avoiding space leaks at all costs
kodimensional.dev
kodimensional.dev
Of course, this is simple - you just enable a language extension so that the language you are referring to Haskell is no longer really Haskell. I think Haskell is a fine language, but given the amount of various language extensions that are needed for it to be usable in production without friction, it seems it will forever be a research language - and there is nothing wrong with that.
Would you say that a company that uses a subset of C++ as a policy is not using C++? Their definition of the subset is as arbitrary as another’s that uses BangPatterns.
Linux won't compile in MSVC. Obviously it needn't, it's a kernel and not a Windows program, but it also can't, because it needs GNU extensions, which are now also implemented for Clang.
If you write broadly cross platform software for C++ you are committed to supporting all three compilers and so that's effectively just the standard. Indeed if all three do or don't do something de facto that is the standard. The ISO document used to say C++ has optional garbage collection, it doesn't because none of the three compilers have that. The ISO standard still doesn't say pointer provenance is a thing, but all three compilers require it, so in fact it's a thing.
I'm intrigued; what was this feature supposed to look like?
The standard basically said, if your compiler supports this new feature, here's how you use it in your programs. And no compilers have ever supported the feature. So C++ 20 rips it back out.
It’s not really all the different from Python PEPs or Rust’s various proposals / nightly stuff.
Just about every language has some form of this.
It is good for people to have ways of playing with a language so it evolves. It is (IMO) bad to suggest the adoption of these things as a means of making a language production-ready.
And the extensions in the article are for the most part in the GHC2021 standard. So it’s more like, this is in C++20.
Younger languages like Rust or Kotlin or something have less pressure for mechanisms like this because you have to get to a certain point in the language lifecycle before the tradeoff tilts from “let’s do a breaking change at a point release” to “let’s flag in the new behavior”, but if (as I expect) e.g. Rust lasts as long as C++ or Haskell, it will be be bedazzled with all kinds of flags and extensions and stuff too, how else do you grow a language for 40 years?
You could introduce things in one version of the standard, and then remove them in the next release if people didn't like it, such that to get the feature you have to specifically use --std=2022a, no earlier, no later; and if you want your project to track the evolution of the language rather than getting stuck on a particular version, you'd have to rip use of such features back out.
You could sort of think of it as if you had a dependencies lockfile which includes a version-constraint on the major version of the compiler; where every time there's a change in what code is accepted, that major version of the compiler goes up. Any change other than changing what code is accepted as valid goes into the lower semver fields; and gets replicated across all major versions of the compiler, where relevant. (Except that you don't have to do this by actually maintaining separate major-version branches of the compiler for every standards release; you can do it the way we currently do feature flags, only the only flag you get is which standard-version your project is written for.)
I don't think I've ever seen anyone actually do this for a language/compiler; but it's a common approach used for protocol and API-client libraries, and seems to work well there.
Haskell is de facto a single-implementation language: an extension is not going to be made incompatible with some future Haskell standard without having been long since deprecated in GHC.
This isn't really a problem in the real world, only in theory.
That said, I am a fan of lazy evaluation in the right circumstances, and often find myself implementing lazy data structures (particularly in my Ruby programs).
Ultimately, though, I’m confused by this article which seems to be all about removing laziness from Haskell in ways that seem entirely counter to what I take to be its purpose. If you have to bend over backwards and rewrite your code in awkward ways to get the language to behave the way you want, maybe the project itself isn’t right for the language? It’s one thing to hack around a couple of isolated space issues in a larger context, but if you are enabling language extensions across the board by default to prefer eager evaluation, why are you using Haskell at all?
It's an attractive (I'm going to say "it seems" though you ought to take that for granted) intellectual rathole. Like some forms of philosophy, like many things. Thank GOD (despite being unbelieving at the time) I went with Lisp instead. And I got real speedups.
It’s also notoriously opaque, which makes it especially hard to know that so-and-so is wasting their time.
How about “I don’t like Haskell and/or OCaml because X, I recommend Lisp instead because Y” as an alternative formulation?
That one I’d be interested to read!
Sounds like you are biased to conclude it has no purpose to begin with.
But every language that beats Darwin for long enough does so by iterating the stuff that can be “fixed” and developing conventions and tools for the gotchas that can’t (practically) be.
My smooth practical brain struggles in languages that aren't Haskell.
This can be a real problem, for example if you read potentially large network input eagerly and use it to compute a small result lazily. IMO it's always a mistake and IMNSHO the proper solution is always something more nuanced than just "make it eager" or "make it properly lazy".
If things are lazy by default, you simply need to put a "!" before the term to evaluate in order to get eagerness.
If things are eager by default, you need to rewrite the algorithm as a streaming algorithm.
One example would be Eigen: https://eigen.tuxfamily.org/index.php?title=Main_Page
See also their page on lazy evaluation: https://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html
Of course, C++ is powerful enough that using specific tools can make the expression template abstraction fall apart (e.g. auto), but fundamentally you can write matrix code very cleanly and get great lazy-evaluated performance.
There was a good Reddit thread recently where a lot of experts weighed in: https://www.reddit.com/r/haskell/comments/wpbs4z/what_things...
I recommend reading the thread -- lot's of great stuff in there, but...
TL;DR: It's a lot more complicated than it seems at first and it's far from obvious that strict-by-default is worth the cost.
Since pure functions compute the same results under lazy or strict evaluation and require that any data dependencies they have are explicitly provided as inputs, they interact with lazy computations in a much more tractable way. This means that adding a strictness operator to a lazy language is much easier than adding a laziness operator a a strict language.
An alternate approach is what python did with generators where there is a data type for lazy computation, but it lives apart from the rest of the language, so it is mostly used for e.g. stream processing where a default-lazy approach is conceptually straightforward and is less likely to lead to extremely non-trivial control flow. This approach does, however, basically give up on having a laziness operator that will turn a strict computation into a lazy one.
lazy by default enables better composition and local reasoning.
https://publish.obsidian.md/paretooptimaldev/Advantages+of+l...
That's wrong. With the bang pattern, evaluation only goes up to WHNF, not the whole way — it's just a syntax sugar around "seq" which has been around since always. If you need to fully evaluate the term, look at the deepseq package: it's implementation is non-trivial.
Sure, GHC goes to heroic lengths and turns as much lazy computations as it can into eager ones, but there is always a limit to what it can do on its own, and you inevitably end with the programmer having to slap strict patterns and seqs and deepseqs wherever they can reach.
The ergonomics become incredibly bad, unfortunately.
(Also see my link to a Reddit thread elsewhere in this thread further ideas on why strict-by-default is not a simple win.)
I think it might actually have been a mistake to have the lazy variants of these data structures, esp. Text/ByteString. IME it's extremely rare to want laziness for these... and it's usually just when building output strings (where there are better solutions like Builders). Anyway, I digress...
IME the same applies to lazy Map/Set, but I'm less familiar with uses for the lazy versions of these. (IIRC they're only spine-lazy, which is rarely what you want, tho.)
> And in LISP IIRC lazy data are type-compatible with strict data.
LISP is dynamically typed -- it's trivial to do there. Also not the directionality: You can go from lazy to strict (that's just evaluation which the runtime will do for you), but you can NOT go the other way automatically. Recovering laziness from strictness is impossible.
The problem with the split happens when you have mismatching types that your compiler will shout at you for.
This is why I'll never really trust Haskell to be usable in a serious context. Haskellers will talk your ear off about how such restricted abstractions enable the compiler to optimize it to hell and back, and perfectly idiomatic Haskell can be compiled to almost-perfect machine code, but in point of fact GHC basically never does, and you still have to care about things like tail calls and design APIs to do things like continuation passing.
That is incorrect, e.g. Java Streams (Javas built in library for functional processing of data) are lazy, which can surprise beginners. You say it's just an std lib and not a lazy programming language? Ok, the much older (but just as lazy) C# linq is directly integrated in the C# language, making the C# language both eager and lazy by default, depending on which parts you use.