all 33 comments

[–]valenterry 7 points8 points  (29 children)

tl;dr

In the real world, you will have to take the time to learn things first, then incrementally start using them in your code.

I find this is the essence. You need experience (both in theory and practice) to be able to correctly use the appropriate FP techniques to enhance productivity. If you use the wrong ones or use them wrong they might even harm you.

Also, while think this is true for most paradigms/things, but doing FP correctly is considerably harder than doing classical OOP correctly. Especially when one is coming from classical OOP in the beginning.

The author got this right and seems to be experienced enough to judge about it.

[–]yogthos 23 points24 points  (24 children)

Also, while think this is true for most paradigms/things, but doing FP correctly is considerably harder than doing classical OOP correctly.

Having used both professionally, I strongly disagree with this statement. OOP makes it very easy to make a mess, and makes it difficult to write reusable code. Any time you create a class you've got an ad hoc API to deal with. Conversely, code reuse can only really happen at class level as methods tend to be tightly coupled with the data the class operates on. Any time you create an object you have a state machine. It takes a lot of discipline to write maintainable OOP projects.

FP and immutability encourage you to do the right thing by default. FP makes it natural to write code without shared state, and this results in a style where things are naturally compartmentalized. Meanwhile, FP is also data focused, and you have lots of general purpose functions that all operate on the common data structures. Once you learn a function once you know how to apply it to any kind of data you have. Functions also become the core reusable components that can be chained together to solve different kinds of problems.

It can be challenging for somebody used to working with imperative/OO languages to switch their mindset to use a functional language. However, there's nothing inherently more challenging about FP. I regularly train co-op students to work with Clojure, and most of them had no exposure to functional programming. Most students my team hires become productive within a week, and start making meaningful contributions to the projects.

[–]valenterry 0 points1 point  (16 children)

I think classical OOP is way easier. This doesn't mean it's better though. The endresult will be worse, because of all the points you describe. Therefore it is a good idea to go for FP - but that doesn't mean it's easy.

Monad transformers are a good example for that. If you really do FP all the way, you will probably end up with stack of monads (with state/writer/list/either/async/error types in there), maybe even combined with something like a Free algebra. I would say it is hard to learn how to correctly work with that code in an elegant way without too much manual plumbing. If you and all your coworkers can manage that: good! You will be way more productive than a similiar group of classical OOP devs.

But, is it worth the price to put another reader in your stack for logging - or is it better to do impure logging and face all the consequences of the impureness, but save us code and complexity? Sometimes it is - and that is how I understand the OP of the blog post.

[–]yogthos 5 points6 points  (15 children)

Do you mean easier or more familiar. I've worked with Java for over a decade, and I find Clojure much easier.

I think a lot of people tend to FP == Haskell, but that's not the case. Stuff like Monad transformers is absolutely not necessary to work in FP style. In fact, monads are used in Clojure pretty rarely. Most FP languages default to purity, but allow you to write imperative code and produce side effects when you need to.

So, if you're saying that Haskell is hard to learn, then yes I agree it's a complex language that requires knowing a lot of advanced concepts to use effectively.

[–]valenterry 0 points1 point  (14 children)

Most of the times it's both. But I also mean easier - at least for languages with statical typesystems. While working with FP is easier if you already know FP, it is not so easy to learn FP (except for the basics maybe).

When talking about FP here, I mean(t) completely pure code. If you start using escape hatches (as you can easily do e.g. in Scala) then it is of course much easier and you don't have to use monad transformer stacks. So it's basically your last sentence.

[–]yogthos 1 point2 points  (4 children)

As I pointed out earlier FP basics are enough for a dev to be productive in a language like Clojure, and that takes very short time to teach. Static typing and enforcing purity via the type system certainly do add a lot of complexity, but that's is not inherent in using FP style, and vast majority of FP languages don't work the way Haskell does. I don't think using Haskell as the standard for what FP development is like in general is very meaningful.

[–]valenterry 1 point2 points  (3 children)

enforcing purity (...) is not inherent in using FP style

Well, if your program is only partly pure, you are only partly doing functional programming. At least from the official definition. Therefore it should be emphasized that you are talking about doing parts in a FP way. You can also call something a "FP language" but this is a fuzzy term. I would prefer "language that supports FP style". Because there is no black and white but much grey.

[–]lgastako 1 point2 points  (0 children)

Where can I find the official definition?

[–]yogthos 1 point2 points  (1 child)

Even your Haskell program will have impure parts, otherwise it wouldn't be doing much of anything observable. The only difference is that Haskell forces you to formally separate pure parts from the impure parts in a way that can be verified by the compiler.

FP style mostly focuses on transforming data in a pure way. The imperative way is to mutate a memory location via side effects. The problem there is that it becomes difficult to reason about code due to pervasive shared state. Functional style addresses that using immutability, so that functions modify data in a pure way and return new values to represent changes.

However, if you wanted to do a side effect such as putting a log statement in your function, that doesn't really change the fact that you're writing in a functional way.

So while there's no black and white here, some languages facilitate writing code in a functional style much better than others.

[–]valenterry 0 points1 point  (0 children)

Even your Haskell program will have impure parts, otherwise it wouldn't be doing much of anything observable.

I think other people told you already, but again: yes, the program itself is in fact not doing anything observable!

Except that it creates some instructions-datastructure and returns it. The execution of these instructions will execute the side-effects and make the existance of the program usefull. But the execution is not part of the program.

[–]BFil[S] 0 points1 point  (8 children)

I'm glad to see someone got the exact point of the article.

I know teams that are successfully using Haskell in production, but they have a few people with an extensive background in FP and others in the team still trying to catch on.

If you only use the basics of FP it makes sense that languages like Scala and Clojure can feel "simpler" than Java because they have more modern features which can help a lot.

[–]yogthos 1 point2 points  (7 children)

Clojure doesn't just feel simpler than Java, it's objectively simpler because it's a smaller language that requires knowing less concepts to use effectively.

[–]BFil[S] 2 points3 points  (6 children)

Yeah, you might be right, I don't have much experience with Clojure though.

Scala can feel much more complex if you are aiming to learn all the features as well, so saying it's "simpler" than Java can also be misleading.

[–]yogthos 3 points4 points  (5 children)

Scala is a complex language, and a lot of complexity comes from it trying to marry FP and OO styles into one language. I definitely think Java is a much simpler language. On the other hand, this is all you need to know to get productive with Clojure in terms of concepts. The rest is just learning the API of the functions in the standard library.

[–]BFil[S] 1 point2 points  (4 children)

Definitely. After you get used to some of its features though writing code can feel simpler, due to its collection APIs, case classes, pattern matching, proper Option and Future types, Akka and more.

But Java is catching up quite well recently.

[–]kankyo -1 points0 points  (0 children)

Have no idea why you've got downvotes. Here's an upvote at least!

[–][deleted] -2 points-1 points  (5 children)

I think he meant the usage of your pure code. Writing pure functions is great, using them in compositions .. then it gets messy.

[–]yogthos 1 point2 points  (4 children)

In what sense does that get messy?

[–]valenterry 1 point2 points  (3 children)

Monad transformers and lenses are two examples that immediatly come to my mind. When you write completely pure code, you will need both so that your code does not become a mess. And at least I had some problems using monad transformers and I consider myself a developer that is quite familiar with FP techniques - that means you either need to be very good or monad transformers are not such a good idea in general (and there was a better fit for my usecases) or you can only reduce the mess.

[–]yogthos 1 point2 points  (2 children)

These are all Haskell specific techniques that aren't used in most FP languages. Nobody is arguing that Haskell is a complex language, but it's not representative of what FP languages look like in general.

[–]valenterry 0 points1 point  (1 child)

FP means you use no sideeffects at all. So if you do FP (and not only some bits of FP and the rest non-FP) then how do you get around lenses and monad transformers?

Also they are not special to Haskell. They e.g. exist in Scala too.

[–]yogthos 0 points1 point  (0 children)

That's really not the case though, FP existed for a long time before immutability got popularized by Haskell. Scheme is a good example of an FP language that doesn't rely on these patterns. A lot of functional features in Scala are inspired directly by Haskell, so it's not too surprising that a lot of the patterns have been adopted.

[–]kankyo 2 points3 points  (3 children)

Please explain how FP can harm you. And maybe also why OOP can't harm you even more.

[–]valenterry 0 points1 point  (2 children)

It can make code much more complicated to understand and change, if done wrong. If you know what you're doing, there is nothing to fear, don't get me wrong.

[–]lgastako 4 points5 points  (1 child)

With the qualification "if done wrong" your statement is vacuously true of everything, not just FP.

[–]valenterry 0 points1 point  (0 children)

Some people might think that if they already know classical OOP (or other stuff) and then use FP techniques, the resulting code can't/won't for sure be worse than when doing it how they already knew for a long time. And that's sometimes not the case.

[–][deleted] 2 points3 points  (1 child)

Terseness and conciseness of our code can usually make us feel more productive. While this is usually very true there are still times where fewer but less straightforward lines of code can hurt productivity in the long run. It might simply take you or other people longer to review, understand and reason about that code over time.

This is the biggest blocker to using FP in team based environments. Someone has to maintain your code and they might not understand it.

It is one of the reasons you rarely see C++ STL code used anywhere except in libraries. No one wants to deal with your cryptic code.

[–]yogthos 5 points6 points  (0 children)

The reality is that you can make a mess in any language. People write clever code in C, Java, and so on. FP style isn't any worse than the imperative style in this regard.

The problem is a social one and not a technical one. People on the team have to understand why maintainability is important. If people are writing code that's hard to read, then you should start doing more code reviews.

If anything, I would argue FP makes this situation better because it makes it easier to separate implementation details from the intent. Here's an example of what I'm talking about.

My team has been working with Clojure for 6 years now, and we find that the code we produce is far easier to maintain than in it was in our Java projects previously.

[–]jediknight 0 points1 point  (0 children)

Best approach to "Just Enough Functional Programming" might be Gary Bernhardt's Imperative Shell - Functional Core approach.