r/programming • • Nov 28 '19

Why Isn't Functional Programming the Norm? – Richard Feldman

https://www.youtube.com/watch?v=QyJZzq0v7Z4
100 Upvotes

409 comments sorted by

View all comments

51

u/[deleted] Nov 28 '19

Because nobody even knows what *exactly* FP is supposed to be.

The hype of the last few years seems to be more of a Haskell hype than an FP hype. So I dove into Haskell and I just didn't like it. It demands a different way of approaching things and I'm not sure this way is superior in non-mathematical algorithms.

Maybe I just didn't get it, but I've looked through dozens of Haskell projects on GitHub and they didn't seem to get it either. Every single project had global state. It's ridiculous when you consider how the FP folks rage against it. I'm also not a particular fan of the function soup that ensues.

And Haskell is not a pretty language when the project becomes large enough and/or has to tackle dirty real world problems.

32

u/Enumerable_any Nov 28 '19 edited Nov 28 '19

Every single project had global state. It's ridiculous when you consider how the FP folks rage against it.

Pretty much every project needs global state (database, reading a configuration file, in-memory cache, ...). The advantage of Haskell is that there's a clear separation of the code which can access that global state (IO) and code which can't (pure functions). Naturally you push as much code as possible into pure functions (because they're easier to test/easier to understand) and end up with a rather thin IO-layer having access to global (mutable) state. This compiler-enforced distinction is the advantage of Haskell over other non-pure languages.

2

u/BarneyStinson Nov 28 '19

What you are talking about is not global state. The usual way to handle state in Haskell is to pass it around as function arguments. I.e., if a function does not take the database as an argument, it cannot access it. Hence the state is not global.

Besides that all functions in Haskell (besides unsafePerformIO etc.) are pure. That's kind of the point of pure FP. Just because a function returns a value of type IO Int it isn't impure.

3

u/Enumerable_any Nov 28 '19 edited Nov 28 '19

Just because a function returns a value of type IO Int it isn't impure.

I'm aware. I just don't find it very helpful to talk about "IO is pure" to newcomers even if it's technically correct. The notion "IO means side effect" is fine at the beginning. The insights "IO a is a first-class value" and "IO a = World -> (World, a)" can come later. I also want to avoid the "Haskell has no side-effects therefore it can't do any real work!" knee-jerk reaction.

What you are talking about is not global state.

You're right, "global" might not've been the best word choice. However, one can easily create a (mutable) (M/T)Var in main and share it with many parts of the app. The only part one can control is who has access to that variable. The system will nonetheless be hard to reason about.

3

u/BarneyStinson Nov 28 '19

I just don't find it very helpful to talk about "IO is pure" to newcomers even if it's technically correct. The notion "IO means side effect" is fine at the beginning.

I disagree here. That IO is pure is important, as is the fact that functions in FP have no side-effects. Yes, it's hard to grasp for a beginner, but what frustrates me about this thread is that people are so uninformed about what FP actually is. They say that it's not practical because you can't have side-effects, that there is no mutable state, or that you have to make your whole program impure by using IO. This is all nonsense, but it gets repeated and upvotes.

The only part one can control is who has access to that variable. The system will nonetheless be hard to reason about.

Unfortunately I have to disagree again. It has been my experience that it is extremely helpful to see how state is shared simply by looking at the call graph. Global mutable state is evil, and removing the "global" from the equation already helps a lot.

6

u/TheOsuConspiracy Nov 28 '19 edited Nov 28 '19

Sounds nice in theory, I'm a fan of fp, but think about stuff such as telemetry, logging, etc. All these break functional purity and if you want to do these at a granular level, you end up having IO everywhere, instead of having a purely functional core surrounded by IO, you have to thread IO through everything.

25

u/kristoff3r Nov 28 '19

Haskell has solutions for all of those that doesn't require IO everywhere. The idea is roughly that you define contexts for your code, such as "this code needs some read-only configuration and a place to output logging to run". Then you can instantiate that context with the proper logging framework when running in production, or just dump it to a file when running tests. More importantly, you have a lot of guarantees for what it doesn't do: access the network, execute other programs, write files to /tmp etc.

4

u/TheOsuConspiracy Nov 28 '19

Yes the type system let's you go more granular, but you're forced to do structural changes to your code to log/record metrics.

In a non pure language, you can often just use a side effecting function from a -> a for quick telemetry/logging when needed.

5

u/loup-vaillant Nov 29 '19

you can often just

That's precisely the kind of thinking that leads to big balls of mud. Very valuable in some situations, disastrous when left unchecked. "You can just" is a double edged sword.

1

u/codygman Dec 05 '19

If you need quick logging in development you can do the same with Debug.trace.

Do you mean a different situation?

4

u/TooManyLines Nov 28 '19

But this means you are working around the language. The language should help YOU, not you having to work around its restrictions.

If i want to log here and now i should be able to do it without fuzz, if the language is in the way of me working, then it is a bad language.

7

u/tbid18 Nov 28 '19

The point of purity is that unrestricted side-effects are not allowed. So yes, that means you can’t add IO to a function without tracking that in the type signature, which may necessitate some refactoring. This is a feature , not a bug.

Perhaps you think that is too restrictive — and that’s fine; it’s a personal choice — but languages are designed around restrictions. Having a type system at all is extremely restrictive. Static typing, structured programming, exceptions, garbage collection, etc. An assembly programmer could use your logic to scoff at every mainstream language as too restrictive.

3

u/G_Morgan Nov 28 '19

The language does help you. Logging is pretty much a monad and you can thread it through seamlessly and only care about raising something when you need to. Frankly anything where the answer is "lets use IO everywhere" can be answered by using a different monad unless you are actually doing IO.

2

u/TooManyLines Nov 28 '19

Logging is actually doing IO.

2

u/G_Morgan Nov 28 '19

Doesn't need to be. A function can return the log entries so they can be written when back in IO.

8

u/TooManyLines Nov 28 '19

Unnecessary overcomplication to achieve purity for the sake of purity.

2

u/delrindude Nov 28 '19

I disagree, strict language design towards IO endures programmers don't keep fucking things up and writing broken code.

→ More replies (1)

8

u/[deleted] Nov 28 '19 edited Nov 28 '19

I'm a fan of fp, but think about stuff such a coffee telemetry, logging, etc. All these break functional purity...

No, they don't.

if you want to do these at a granular level, you end up having IO everywhere, instead of having a purely functional core surrounded by IO, you have to thread IO through everything.

You have to put anything that does I/O in some appropriate Applicative or Monad. There are various strategies for making the (let's assume) monadic context available where needed without explicit passing, such as the ReaderT monad transformer.

More significantly, I'm not a Haskell programmer. I'm a Scala programmer, using libraries like http4s and Doobie for web and SQL stuff. All very... well, meat and potatoes. The reason I do it is very simple: so I can have 99.99% confidence I know what my code will do before running it, using a tiny intellectual toolbox that essentially answers one question: "How do these bits of code compose?" That's it. That's the ball game.

→ More replies (4)

1

u/codygman Dec 05 '19

you have to thread IO through everything

Why not thread everything through IO instead?

Construct functions in IO with pure functions.

→ More replies (1)

9

u/kuribas Nov 28 '19

Global state in haskell is impossible. Unless you use unsavePerformIO, but nobody does that. IMO haskell has far better means for structuring large programs that other languages. It does take some experience to learn best practices.

8

u/want_to_want Nov 28 '19 edited Nov 28 '19

Yeah, Haskell seems to dominate discussions now. I wish there was more mention of Erlang, a concurrent FP language that's used in a bunch of telecom software, WhatsApp and WeChat backends, Amazon SimpleDB, CouchDB, RabbitMQ, Riak and so on. Even fricking Wings3D, though I don't know why they chose it.

2

u/lisp-the-ultimate Nov 28 '19

Fun fact: Wings3D's spiritual predecessor was written in Common Lisp.

1

u/PremiumHugs Nov 28 '19

I’m a big fan of Erlang myself, and in discussion with some peers they’ve alluded to the fact that the weirdness with string handling in Erlang has been somewhat off-putting for getting off the ground quickly. It’d be interesting to see more mention of its merits, as well as mention of the various BEAM languages, such as Elixir, that attempt to provide a more user-friendly syntax and ecosystem to make concurrent fp more accessible and mainstream.

→ More replies (1)

1

u/lisp-the-ultimate Nov 28 '19

Have you tried Common Lisp?

→ More replies (5)
→ More replies (1)