TBH, when you have SPJ forwarding something by Jose Valim, you’re likely to get a very interesting and informative thread.
This thread brings 5 things to mind:
- For schemes (Haskell recursion schemes applied to for loops)
- Insufficient power of optics / zip?
- Haskell foldr is literally just an implementation of for (re: Probie)
- Labeled gotos might still be a good idea / Walrus operators in Haskell
- Benchmarking against Python via “Impractical Python Programs” and “Big Book of Small Python Projects”
For Schemes
Recursion is equivalent in power to while, and higher-order functions can be seen as replacements for for-loops. The advantage of higher-order functions, or specialized syntax resembling for-loops, over for is roughly the same as the advantage of for, while, and function calls over the traditional goto. Goto is simply too powerful, and is not expressive enough to indicate the intent of its use. Likewise, for can be seen similarily as too powerful (Go, for instance, lacks while, and implements all looping via for), and more specialized higher-order functions (or extensions to the for expression syntax, in Elixir) might be a better way to do it; i.e, the specialized higher-order functions represent specific uses of for and convey intent better.
This implies For Schemes, as an analogue to Haskell’s recursion schemes, i.e, a way to taxonomize and analyze things people do with for. For functional languages, this is particularly useful in the sense that if someone coming in from an imperative language wants to reach for a for loop, there’s an existing functional alternative that achieves the same result. For imperative and more traditional languages, as long as there’s compiler / interpreter support, you get the effects of being more specific than a for loop without sacrificing performance, as with Javascript array methods.
Ideally, we should be able to get “for loop considered harmful” in the same way “goto is considered harmful”.
Insufficient power of optics / zip
I’m not sure if this has been done already, whether it’s possible, but if you go from the idea that “higher-order functions are a better replacement for for”, and we can’t do this easily with optics and zip (but can with mapAccumL in Haskell / map_reduce() in Elixir), it implies that we are missing a specialized higher-order function for this task.
If I understand correctly, this particular problem resulted in the addition of let to for expressions in Elixir (Introducing `for let` and `for reduce` - Official Proposals - Elixir Programming Language Forum ), presumably over map_reduce(). Likewise, if existing features in optics and the zip function is insufficient, it implies that we might need new functions to automate this simply.
Haskell foldr is just an implementation of for (Probie)
I guess this is old hat, but it should be emphasized more that everything anyone can do with for, anyone can do with Haskell foldr (although not necessarily foldl’ / reduce). reduce holds an accumulator, i.e, implicit state, updates the state, but cannot short-circuit, as lazy-right folds can. foldr is powerful enough to store state in an external accumulator, build a continuation pattern from the original data structure, then implement any recursive pattern necessary (i.e, map, mapAccumL, filter, foldl’, traverse, etc).
Labeled gotos might still be a good idea. / Walrus operators in Haskell
Here’s a certain declarative programming problem in Haskell. A certain declarative style in Haskell completely eschews the use of lambdas, considering them insufficiently declarative, and relegating them to where clauses. I.e, you end up with named blocks in Haskell; what you’d do in another language with a block, such as a loop, you name instead and render a function, hopefully making the code clearer.
However, if you compare it to the Python, this can actually make the code less clear, because in Python, you have immediate access to the block for viewing, whereas you’d have to scan to the where clause or let declaration in Haskell.
In Haskell, a potential solution might be an equivalent to a Walrus operator, i.e, -XLetLambda / -XLetWalrus, to provide immediate use after declare.
Right now, if we want to do this, we’d have to do let foo arg = arg in foo
, which can be unnecessarily verbose. A solution might be a language extension to the syntax allowing foo\arg -> arg
syntax, i.e, the lambda is immediately used after declaration. We can also extend this to foo\->3
syntax for variable declarations.
Conversely, for more traditional languages, you might wish for a hoisting macro, i.e, a block of code has the macro applied, and it is now hoisted to top level, providing a name for the block, as well as optionally allowing reuse by creating a function that takes arguments filling in out-of-scoped names.
Benchmarking against Python
Lastly, I think Haskell losing to Python in expressiveness is pretty bad, because if you check out Hutton’s “Programming in Haskell”, he makes the claim that Haskell can be 2-10 times shorter than C, whereas Python claims to be 2-5 times shorter than C. When you look at functional Python, the gap obviously narrows tremendously, but Python doesn’t support functional programming well at the implementation level, and Haskell’s syntax is optimized for functional programming.
But benchmarking against Python is useful, since Python is considered the gold standard as a combination of readability and expressiveness, and moreover, Python is pretty good when it comes to its overall ecosystem.
There are a few open Python books introducing a bunch of newbie projects, say: Impractical Python Projects ( GitHub - rlvaugh/Impractical_Python_Projects: Code & supporting files for chapters in book ) and The Big Book of Small Python Projects ( GitHub - asweigart/the-big-book-of-small-python-projects: The source code for the programs in "The Big Book of Small Python Projects" ). I’ve ported one from the latter myself ( New, Average, and Pragmatic: Translation of Vigenere Cipher from "The Big Book of Small Python Projects" into Haskell ), but the Python books present an opportunity to benchmark the ecosystem maturity in your own language, the expressiveness and maintainability of the language, and present a possible Rosetta stone for newbies to grasp and hang on to.
Projects where you end up being significantly more verbose than normal can present possible pain points that need to be resolved, and projects where there are no good corresponding libraries in your own language might highlight points for improvement.