But no, it's pretty complicated: https://wiki.haskell.org/Memoization you even have to involve fixed point combinators. Pretty disappointing.
But no, it's pretty complicated: https://wiki.haskell.org/Memoization you even have to involve fixed point combinators. Pretty disappointing.
Just like in Python...?
Wow. I don't remember reading such blasphemy in other Haskell docs. And I like it, no joke. Though it's likely about the performance characteristics; memoize probably behaves (and not just appears to behave) like identity modulo performance, that's just not documented clearly enough.
Take lenses for example. All of this unnecessarily complicated shit because Haskell doesn’t have any reasonable record syntax.
I appreciate Haskell as a research project and it’s clearly pioneered many important FP concepts: monads, free monads, trampolines, etc. But I would never ever choose it for a new project. OCaml, F#, and Scala would be my go to. Nice FP, but not so opinionated to cripple/slow you down when you want to dip your toes in mutation. And in my experience, almost all non-trivial programs require at least a little bit of mutation. And when it’s required, I really don’t want to waste my time with IORefs or STRefs or whatever.
Lenses (in the original, pre-Laarhoven form) are literally the same as properties in, say, C#. The extra complexity in their modern form is to pack the setter and getter into a single function, and allow in-place modification (without going throgh getter and then back through setter). The extra-extra complexity in the lens package is the result of Edward Kmett et al generalizing it to support traversals and pattern matching, while also providing support for the entire standard library and the kitchen sink.
As for mutation, I agree. I think Rust with hihgher-kinded polymorphism would be my ideal language.
> The extra complexity in their modern form is to pack the setter and getter into a single function, and allow in-place modification (without going throgh getter and then back through setter
In place updating of nested fields is pretty much the bare minimum that any respectable record system provides.
EDIT: And to clarify, by in-place updating I meant in-place semantically, not syntatically (the old solution of keeping a record of two functions can do the syntax part too). But I think I probably misremembered how van Laarhoven lenses work and they might not be ultimately different from getter-setter chaining, sorry.
Also I don't see how ST would especially slow you down if you really want to write pure function with mutability under the hood? It's an interface you have to learn, but the benefit is you _prove_ your mutations are locally-scoped.
Moreover it’s not just about performance. Right now I’m programming a bittorrent client in Scala with Akka and it would incredibly difficult to do without mutation. And the complexity overhead that Haskell requires for mutation just isn’t worth it in a lot of cases.
I’m pretty good at determining if mutation doesn’t bleed into the outer scope and most of the time don’t want to pay the complexity overhead in actually proving. Sometimes you do want that of course, but not most of the time.
It seems a lot of Haskellers have super high IQ, and can grok Haskell Lens like it's a toy truck. Then they write a blog post that's pretty hard for a beginner to disect.
Or maybe they struggled with Lens but by the time they spent 5 years with other gurus in a professional setting they finally got it, but have forgotten how hard it is to learn this stuff.
You also don't have to learn all of it to start using it. The majority of code I write in Haskell doesn't use anything more fancy than function composition, ADTs, and some type classes. You can do programming at the type level but that's not a requirement for entry.
I find it hard to co-sign on your assertion that all non-trivial programs require mutation. This hasn’t been my experience at all. While having access to side-effects is certainly essential (which Haskell is fully capable of doing), mutation is quite rarely needed in my experience. In the cases that it is, IORefs are trivial to use. On top of that Haskell has MVars, which I hazard to claim are the best concurrent mutation primitive I’ve ever had the pleasure of using. Quite the opposite of being crippled.
Memoising a function f in Haskell can be done like this: create some lazy map where the keys are arguments, and the associated value is f(argument). The function f will only be evaluated once you actually try to use the values of the map.