← Back to context

Comment by initplus

3 months ago

Yes - the value of functional programming isn't that working in OCAML, or F#, or Haskell is 10x as productive as other languages. But that it can teach you worthwhile lessens about designing software that apply equally to imperative languages.

Modelling the business domain, reasoning and managing side effects, avoiding common imperative bugs, these are all valuable skills to develop.

F# is a great language to learn, and very approachable. Worst part about it is interacting with antiquated .NET API's. (I can't believe the state that .NET support for common serialization formats is still in...)

> Yes - the value of functional programming isn't that working in OCAML, or F#, or Haskell is 10x as productive as other languages.

This is not true in my personal experience.

As has been famously said (paraphrased): Functional programming makes tough problems easy and easy problems tough.

In other words the value of functional programming depends on your domain.

  • It’s only tough to change your way of thinking. Most people making the switch find it tough because they are trying to find imperative techniques to do something in a functional way and struggling because they can’t find an if else statement or a for loop. But if you were never taught to think in terms of conditional branching or looping indexes you’ll save a lot of time.

    • Exactly. I've long held the sentiment, that pure functional programming is easier than imperative programming. The only hard part, is switching from an imperative mindset.

  • Maybe my phrasing is not clear - I meant that these languages are indeed not significantly more productive.

    • By what measure? Haskell can be a huge productivity multiplier. The standard library is built upon many powerful, unifying and consistent mathematical abstractions. For example, there is almost no boilerplate to write for any traversal, mapping, error handling etc. The average Pythonista simply has no idea what they are missing. But Haskell doesn't have anywhere near the third party ecosystem of Python, so is less productive by some measures.

    • But (and I agree with the GP) they are. They are overwhelmingly more productive, in a way that you often can't even compare quantitatively.

      They are also a lot less productive. It depends entirely on what you are doing.

  • > easy problems tough.

    That needs a qualifier: it can make easy problems tough if you're not familiar with how to solve them in a functional context.

    A big part of that is because smart people have already solved the tough problems and made them available as language features or libraries.

    • > That needs a qualifier: it can make easy problems tough if you're not familiar with how to solve them in a functional context.

      All problems are easy if you are familiar with how to solve them. Unfortunately it's part of the problem to find out how to solve them, and that can be unusually hard in case of functional programming. Like solving something with recursion instead of loops + states. There is a reason cookbooks use loops not recursion.

    • Not really, certain problems are just inherently harder to express in a purely functional way than they are in an imperative way (and the reverse is just as true). For example, computing a histogram is much simpler in imperative terms (keep an array of histogram values, go through the original list, add 1 to the array element corresponding to the current element in this list) than in a purely functional style, especially if you need a somewhat efficient implementation.

      My favorite example of this is implementing quicksort. It's significantly easier in C than it is in Haskell.

      7 replies →

  • > makes tough problems easy and easy problems tough

    And because of mutual recursion, that means that tough is easy (and easy tough). In other words, if we call the class of tough problems T and easy problems NT, we have T==NT, given FP.

  • What easy problems are tough in F#? I’ve been using it for writing random scripts and as a Python replacement.

    • It's an overgeneralisation.

      If you try and turn F# into Haskell at home you may run into that problem.

      F# is functional first language so if an object oriented or procedural solution is the right call the options right there when you need it.

    • Writing recursive descent parsers in F# is a lot of fun with ADTs and pattern matching.

  • How do you “Hello World” in a functional language? Doesn’t it have side effects?

    • Yes, and AFAIK, you're pretty much free to cause side-effects in functional languages; it's just a bit awkward and somewhat discouraged. It's kind of like how C still has goto, yet it's still a structured programming language.

      Even in Haskell, which tries to control side-effects more, it's not hard; it's just that it's stuck with an "IO" annotation. Any function that calls an IO function also becomes an IO function; it's infectious.

        main :: IO ()
        main = putStrLn "hello, world"

    • There's some real confusion about what "functional" means, because it depends on who's speaking. Writing _exclusively_ pure functions is a Haskell thing. A much looser definition of the functional style would be to say that you mostly write pure functions over immutable data, but when you have to actually do a side effect you write a procedure that talks to the outside world and go on with your day. If you go digging in this site's archives from about ten years ago, I recall numerous debates about what constituted functional programming, and whether the latter counts at all. But if we _are_ talking about Haskell, the answer to your question is obviously "Monads."

    • It has an effect. Whether it's a "side effect" depends on how we've defined that.

      One way of viewing Haskell is that you are lazily constructing the single composite "effect on the world" called main.

          helloWorld :: IO ()
      

      then is a value representing an effect, but it only actually happens when it becomes a part of main.

      Threads complicate this but don't completely destroy the model.

Hot take of the day: you learn that with imperative programming just as well.

I familiarized myself with fp to the point of writing scheme and haskell around 15 years ago. Read the classics, understood advanced typing, lambda calculus and so on. The best “fp” I’m using nowadays is closures, currying in the form of func.bind(this[, first]) and map/filter. Which all are absolutely learnable by the means of closures, which are useful but I can live without. Sometimes not having these makes you write effing code instead of fiddling with its forms for hours.

Still waiting for the returns from arcane fp-like code I produced earlier. Cannot recognize nor understand none of my projects in this style that I bothered to save in vcs. Imperative code reads like prose, I have some of it still in production since 2010.

These FP talks are disguised elitism imo (not necessarily bad faith). Beta reduction and monadic transformers sound so cool, but that’s it job-wise.

  • > These FP talks are disguised elitism imo (not necessarily bad faith). Beta reduction and monadic transformers sound so cool, but that’s it job-wise.

    They may be disguised mathematics. People are into math because it is neat / elegant / cool. So they study it regardless of whether it has a practical use or not.

    • Some programmers have serious math envy. This can be good if they are self aware about it and keep it in check, because it makes them better programmers. Otherwise they can a pain to work with. Seniors should be people that have dealt with this aspect of their own talent, not juniors who are promoted in spite of or because of it

  • I commonly implement things in an imperative style as a quick hack, then if it gets use I translate it into a more functional style. It kind of just happens as I clean it up and refactor during revisits.

    It might be a matter of taste, but I enjoy code built with functional abstractions that allow neat composable data flows and some caches loitering around. I find it also helps when adding UI. Sometimes performance could be better with mutation, but when I'm at that point I've already spent much more time tuning the thing with caches.

  • In theory, you could pick up your one language, say, Java, and through the course of a normal career learn everything necessary to program in that language in the best possible way.

    In practice, it's a pretty well-known phenomenon experienced by many skilled programmers that being forced into different styles by different languages results in learning things that you would only have learned very slowly if you had stuck only to your original language. To be concrete about the "very slowly", I'm talking time frames of your entire career, if not your entire life and beyond. It would be a long time programming in Java before you discover the concept of something like "pure functions" as a distinct sort of function, a desirable sort of function, and one that you might want organize your programming style around.

    Of course, having heard of the concept already, we'd all like to fancy ourselves smart enough to figure it out in less than, say, three decades. But we're just lying to ourselves when we do that. Even the smartest of us is not as smart as all of us. You are not independently capable of rediscovering everything all the various programming communities have discovered over decades. If you want to know what even the smartest of us can do on their own without reference to decades of experience of others, you can look into the state of computer programming in more-or-less the 1980s, 90s if you're feeling generous. I think we've learned a lot since then, and the delta between the modern programmer and a 1980s programmer certainly isn't in their IQ or anything similar, it is in their increased collective community experience.

    By getting out into systems that force us to learn different paradigms, and into communities that have learned how to use them, we draw on the experience of others and cover far more ground than we could ever have covered on our own, or in the context of a single language where we can settle into a local optima comfort zone. Jumping out of your original community is kind of an annealing process for our programming skills.

    "The best “fp” I’m using nowadays is closures, currying in the form of func.bind(this[, first]) and map/filter."

    That is really not the lesson about software design that FP teaches, and blindly carrying those principles into imperative programming is at times a quite negative value, as your experience bears out. FP has more to say about purity of functions, the utility of composition of small parts, the flexibility of composition with small parts, ways to wrap parts of the program that can't be handled that way, and providing an existence proof that despite what an imperative programmer might think it is in fact possible to program this way at a system architecture level. I actually agree 100% that anyone whose takeaway from FP is "we should use map everywhere because they're better than for loops and anyone who uses for loops is a Bad Programmer" missed the forest for the leaves, and I choose that modification of the standard metaphor carefully. I consider my programming style highly influenced by my time in functional programming land and you'd need to do a very careful search to find a "map" in my code. That's not what it's about. I'm not surprised when imperative code is messed up by translating that into it.

    • > FP has more to say about purity of functions, the utility of composition of small parts, the flexibility of composition with small parts, ways to wrap parts of the program that can't be handled that way, and providing an existence proof that despite what an imperative programmer might think it is in fact possible to program this way at a system architecture level.

      Adding to that, in my case it also made realize that deterministic elimination of entire classes of errors in large, complex code bases, in a systematic rather than ad-hoc way, is actually possible. Prior to discovering fp, and particularly Haskell's type system, I spent much effort trying to do that with a combination of TDD and increasingly elaborate try/catch/throw error handling. Discovering Haskell's compiler, type system, and monadic quarantining of effects obsoleted all that effort and was a huge eye opener for me. And a nice side-effect is easy, reliable refactor-ability. Being able to apply those concepts to imperative and other programming paradigms is where the real value in fp is, imho. Programmers still wrangling with the Tarpit [1] need to take a look if they haven't already.

      [1]:https://news.ycombinator.com/item?id=34954126

    • That might be, probably is. But the representation of FP gets mostly done by those who are only halfway there, creating an impression that it is a better way of programming overall, when it’s just a mixed bag of approaches dictated by a set of esoteric languages (from business pov). The worst part is that it doesn’t translate verbatim to any non-fringe language and creates a mess in it, due to adopted inertia. At least that is my experience with FP “recruitment”.

      I wish I skipped this FP tour completely and instead learned how to structure my programs directly. Could save me a year or five. Maybe there’s no better way, but in practice clear explanations are always better than these arcane teachings where you repeat something until you get it by yourself.

    • "In theory, you could pick up your one language, say, Java, and through the course of a normal career learn everything necessary to program in that language in the best possible way."

      OK, then you know about currying, immutable data structures, map/reduce/filter, &c.

      Because Java has that since way back when. No real closures, I think, but that doesn't matter much because the anonymous functions do what you want pretty much all the time and you could probably invent your own closures if you really want something else.

      2 replies →

Ocaml definitely doesn’t make you more productive

  • I have not used OCaml, but presumably Jane Street thinks OCaml makes their coders more productive.

    • I've looked at their code and, I would not wish it on anyone to work there, it's on par with large Java codebase at a bank institution

I wouldn't even say antiquated, modern .Net APIs can suck to work with too, the entire ecosystem is written for C# ASP.Net core and everything else feels second class.

I love F#, the working with C# elements of the language drove me away.

  • Which elements were the source of pain?

    Writing web applications and back-ends in F#, as far as I'm aware, is straightforward and a joy because of integration with the rest of ecosystem.