Hacker News new | ask | show | jobs
by kmill 1013 days ago
Currying is pretty much just a convenient way to write a one-constructor one-method class. It's a parameterized version of the command pattern.

That said, if you're using a language with classes you may as well stick with those and omit the syntactic sugar from your diet.

I find functional programming useful though because it can shorten code in a way where you can get a better view of the big picture and easily make sweeping changes. But it's also easy to write in a way that's too "perfect" (i.e. resistant to change) and incomprehensible. Though I'm not sure the total incomprehensibility is generally any worse than an enterprise Java codebase -- local readability might only give you the illusion that the whole system is understandable. Still, if you don't enjoy reading code in a certain style it makes for hard reading!

1 comments

It's also nice for making certain kinds of code more readable and maintainable in a way that isn't perfectly emulated by anything in OOP.

Here's a nice talk on the subject: https://www.youtube.com/watch?v=sfYA0HCWgqQ

and slides: https://fsharpforfunandprofit.com/pipeline/

This "looser" way of doing things tends to be more popular in the F# community, which isn't terribly fond of those rigid crystalline structures that you get in other corners of the FP community. Though I'd also point out that, even in Haskell, ways of programming that lead to incomprehensible and resistant-to-change code are often viewed as a sign of inexperience by more seasoned developers. There's a lot of truth in this joke: http://pages.cpsc.ucalgary.ca/~robin/class/449/Evolution.htm

I use Lean 4 quite a lot these days, and I appreciate how the developers embrace syntax extension and metaprogramming solutions rather than trying to encode complicated control flow using ascii art operators (it's like Lisp meets the ML family). I suppose programming with dependent types is enough of a challenge, so why make it even harder? They've got a sophisticated "do" notation with imperative constructs, mutable variables (that are translated into something StateT-like), and pretty seamless monad lifting. They've also got pipeline operators, including things like `x |>.foo a b c` for `X.foo x a b c` if `x` has type `X` (type-directed field notation is wonderful).

Many times you don't even use currying, since it's not always worth the effort making a particular argument be the last one. Instead you might write `xs.map (f · x)`, where `(f · x)` is short for `fun a => f a x`.

The language also has an interesting GC that lets you write functional algorithms that have good memory performance -- objects with exactly one reference can be mutated (using so-called FBIP, "functional but in-place"). There's an Array type whose theoretical representation is a linked list but whose run-time representation is a dense array with O(1) pushing, popping, and writing. Makes it easy to write performant code without resorting to sophisticated functional data structures.