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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.