9 ms·
It's way simpler actually. Functional programming isn't the norm because — while it's extremely good at describing "what things are and how to describe relatio
by endymi0n 5y ago
It's way simpler actually.
Functional programming isn't the norm because — while it's extremely good at describing "what things are and how to describe relationships of actions on them" — it sucks at "describing what things do and describing their relationships to each other". Imperative programming has exactly the opposite balance.
I find the former to just be more valuable and applicable in 80% of real world business cases, as well as being easier to reason about.
Entity Relationship Diagrams for example are an extremely unnatural match to FP in my eyes, and they're my prime tool to model requirements engineering. Code in FP isn't structured around entities, it's structured in terms of flow. That's both a bug as well as a feature, depending on what you're working on.
Most of the external, real world out there is impure. External services, internal services, time. Same thing for anything that naturally has side effects.
If I ask an imperative programmer to turn me on three LEDs after each other, they're like: Sure, boss!
for led in range(3): led.turn_on(); time.sleep 1; led.turn_off()
If I ask an FP guy to turn me on three LEDs after each other, first they question whether that's a good idea in the first place and then they're like... "oh, because time is external to our pure little world, first we need a monad." Whoa, get me outta here!
Obviously with a healthy dose of sarcasm.
Don't get me wrong, for the cases where it makes sense, I use a purely functional language every day: it's called SQL and it's awesome despite looking like FORTRAN 77. I also really like my occasional functional constructs in manipulating sequences and streams.
But for the heavy lifting? Sure give me something that's as impure and practical as all of the rest of the world out there. I'll be done before the FP connaisseur has managed to adapt her elegant one-liner to that dirty, dirty world out there.
- dnautics 5y agothere are FPs that have and embrace side effects, not everything is haskell.
- yomly 5y agoYour LED example is an interesting one. In the basic model of a computer architecture, the screen is abstracted as a pixel array in memory - set those bits and the screen will render the pixels. The rest is hand waved as hardware. A pixel array can be trivially modelled as a pure datastructure and then you can use the whole corpus of transformations which are the bread and butter of FP. A screen is as IO as it comes for the most average consumers of a screen, we aren't peeking into its internals. And for me, that's the point of FP - it's not that IO is to be avoided, it's about finding ways of separating your IO from the core logic. I loosely see the monad (as used in industry) as a formalised and more generic "functional core imperative shell" Now when it comes to pure FP languages, they keep you honest and guide you along this paradigm. That said, it's perfectly possible to write very impure imperative Haskell - I've seen it with my own eyes in some of the biggest proprietary Haskell codebases But imperative languages don't generally help you in the same way, if you want to do functional core imperative shell, you need a tonne of discipline and a predefined team consensus to commit to this
- endymi0n 5y agoq.e.d.
- yomly 5y agoWould love to know what has been proved? Very up for an open and honest discussion. I'm back to writing imperative after years of functional. I think it is a very pragmatic choice today to go with an imperative language but I find class-oriented programming to be backwards and I think functional code will yield something more robust and maintainable given how IO and failure are treated explicitly. I'm not quite sure where the balance tips between move fast but ship something unmaintainable vs moving slower but having something more robust and maintainable. Programming in a pure language is quite radical, it's a full paradigm shift so it feels cumbersome especially if you've invested 10+ years in doing something different. I'd liken it to trying to play table tennis with your off hand in terms of discomfort. There are plenty of impure functional languages around - OCaml, Scala, Clojure, Elixir.... And Javascript (!?!?) FP is relatively new as a discipline and still comparatively untrodden. What if equal amounts of investment occured in FP - maybe an equivalent of that ease of led.turn_on will surface. And tbh it probably just looks like a couple of bits - one for each LED and a centralised event loop. Which so happens to have been a pattern which works quite nicely in FP but emerged in industry to build some of the most foundational things we rely on...
- TeMPOraL 5y ago> In the basic model of a computer architecture, the screen is abstracted as a pixel array in memory - set those bits and the screen will render the pixels. The rest is hand waved as hardware. It was. I still remember the days. It was nice to be able to put pixels on the screen by poking at a 2D array directly. It simplified so much. Unfortunately, it turned out that our CPUs aren't as fast as we'd like them at this task, said array having 10^5, and then 10^6 cells - and architecture evolved in a way that exposes complex processing API for high-level operations, where the good ol' PutPixel() is one of the most expensive ones. It's definitely a win for complex 3D games / applications, but if all you want is to draw some pixels on the screen, and think in pixels, it's not so easy these days.
- dwohnitmok 5y agoI have my share of gripes about Haskell (which I'm assuming is the language you have in mind when you're talking about a pure FP language), but even with the sarcasm disclaimer, this is a pretty extreme strawman. This is the equivalent Haskell. turnOnThreeLEDs = for_ [1..3] (\i -> do LEDTurnOn threadDelay (10^6) LEDTurnOff ) or all in one line for_ [1..3] (\i -> do { LEDTurnOff; threadDelay (10^6); LEDTurnOff }) It looks basically the same. EDIT: I would also strongly dispute the idea that FP is structured around flow instead of data structures. In fact I'd say that FP tries to reduce everything to data structures (this is most prominently found in the rhetoric in the Clojure community but it exists to varying degrees among all FP languages). Nor is SQL an FP language (the differences between logic programming a la Prolog and ultimately therefore SQL and FP is very very different). FP's biggest drawback is that to really buy into it, you pretty much need a GC. That also puts an attendant performance cap on how fast your FP code can be. So if you really need blazing fast performance, you at least need some imperative core somewhere (although if you prefer to code in a mainly FP style, you can mainly get around this by structuring your app around a single mutable data structure and wrapping everything else in an FP layer around it).
- systemvoltage 5y agoWhat you wrote is imperative despite the fact that it’s written in Haskell.
- dwohnitmok 5y agoI mean if pure FP is enough to write imperative code as well then the distinction between the two doesn't seem all that important to me. What would be a non-imperative equivalent to illustrate your idea?
- Jensson 5y agoSmall examples of mutating state in Haskell are about as meaningful as small examples of pure functions in Java. Small examples are really easy to do and not that ugly, bigger more complex examples don't look so nice. The whole reason people want to use Haskell is because doing complex state mutations is horribly ugly and unergonomic so people don't do it, that is a feature of the language.
- stepbeek 5y agoI'm at a very similar place to you at this point. It make sense for FP to be good at "describing relationships of actions" since the base unit of reasoning is a function, or an action. The beauty of modern programming is that we don't have to stick to a pure example of either paradigm. We can use FP techniques where it makes sense and turn to imperative otherwise. In you example, we could have a nice, purely functional model of an LED that enforces the invariants that make sense. We could then "dispatch" the updated led entity to an imperative shell that actually took the action. All without using the M-word! I'm probably - unfairly - treating your example more seriously than you intended it, but I think I'm leading to the same conclusion as you at a slightly different place. I want to have a purely functional domain that I wrap in an imperative shell. Trying to model side-effects in a purely functional manner using something like applicative functors just doesn't give the productivity boost that I want. > I use a purely functional language every day: it's called SQL This is my favourite way to annoy FP advocates (despite probably being one myself). Every one is a closet mathematician in FP-land but no one wants to admit how beautiful relational algebras are.
- TuringTest 5y ago> I want to have a purely functional domain that I wrap in an imperative shell. Trying to model side-effects in a purely functional manner using something like applicative functors just doesn't give the productivity boost that I want. Funtional Reactive is a very good way to create that mix. Web Front developers have realized that, and that's the reason why most modern frameworks have been veering towards this model slowly, with Promises and Observers everywhere. When you represent state as an asynchronous stream that you process with pure functional methods, you get a straightforward model with the best of both paradigms.
- stepbeek 5y agoI like FRP, but prefer to imitate it in a synchronous manner now - I mainly work on the JVM and I've personally found debugging to be too painful when working asynchronously. If I need async then FRP is definitely the first tool in the toolchest that I'd reach for. Elixir's pipe operator is a brilliant tool that I wish every language had. I mainly use kotlin day-to-day and definitely abuse the `let` keyword to try to get closer.
- grumpyprole 5y agoPeople need to be familiar with both approaches. And there's sillyness on both sides. I've seen influential imperative OOP programmers on stackoverflow model a bank account using a single mutable variable, even when they surely know accountants use immutable ledgers. Most imperative languages since Fortran contain declarative elements, otherwise we'd be adding numbers with side-effects. Similarly most FP languages offer imperative programming. But the real power from FP comes from it's restrictions and yes, query languages are one such (excellent) application. Config languages and contract languages are others.
- go_elmo 5y agoI agree with you but see another relevant reason: in FP, you HAVE to consider side effects 1) from the beginning and 2) completely, which as anyone can guess is quite a task. In imperative you can just ignore it and produce objectively worse code, as you are not even aware of all side effects possible. And sure, for the LED project it wouldn't even matter, but the decision FP vs imperative is then more of a design / quality criterion in general - the notion of one being better than the other is just wrong. Also a monad is much more complicated if you don't really understand it which makes judging it a bit unfair
- jstimpfle 5y agoWhat is a side effect? Getting the time? Pushing a result to an output channel? A debug printf? Setting a flag to cache a computation as an optimization? Is it not: evaluating a thunk? Implicitly allocating some memory to store the result of a computation? Haskellers are trained to have a very inflexible view of what a side effect is. It is dictated by the runtime / the type system. In my views, there are lots of things that Haskellers call "side effects" that I would just shrug my shoulder on, and also lots of things that they do not call side effects but I care about them. It really depends on the situation. This fixed dichotomy imposed by the language does more harm than good in my experience. NB: I'm aware that running a computation that for example gets the system time will get a different time it runs. That does not mean that I _have_ to consider it a side effect. I usually do not have a good reason to run the procedure multiple times and expect the runs to be totally identical by all means. In an imperative language, I have very precise control when this procedure runs.
- go_elmo 5y agoApart from language-imposed limitations (haskell is nowhere near the theoretical completeness of category theory, e.g. bottom-type), the "pure" nature of FP forces the use of abstract structures able to handle it (e.g. Monads), thus, wanting to write code, you first need to think even possible side effects trough to be able to write code containing them, which by definition is a stronger criterion of catching unwanted effects compared to imperative, where you can produce whatever you want. And sure, it is in no way a guarantee to produce good code, it is just a stronger condition. It effectively boils down do "assembly is just as good as C", and we see where it took us. Anyone telling me to think it through as rigorous in imperative is lying to him/her self practically, unless they're actually verifying their code.
- chii 5y ago> If I ask an FP guy to turn me on three LEDs after each other, first they question whether that's a good idea in the first place a proper FP engineer would model the problem of turning on LEDs one ofter another as a set of states. A simple way would be a bit set of the LEDs, in an array where each element is the LED's on/off state, like ['000', '100', '110', '111']. Then, the problem decomposes into two, simpler problems: 1) how to create the above representation, and 2) how to turn the above representation into a set of instructions to pipe into hardware (e.g., send signals down a serial cable). The latter problem is imperative by nature, but the former - that of the representation of states, is very pure by design! So the FP model provides a solution that solves a bigger, more general problem of turning LEDs into patterns, and this solution is just one instance of a pattern. So if your boss asks you in the future to switch the bit patterns to be odd/even (like flashing christmas lights), you can do it in 1 second, where as the imperative version will struggle to encode that in a for-loop.
- iainmerrick 5y agoI promise you, a "proper" C programmer will do the same thing, faster. And I say this as an FP fan! A bad C programmer will write horrible spaghetti code, but it will probably be enough to do the job. A bad Haskell programmer will get absolutely nowhere. If you think pure FP is great for this stuff, I think you need to explain why imperative languages are regularly used to win the ICFP programming contest (https://en.wikipedia.org/wiki/ICFP_Programming_Contest#Prizes https://en.wikipedia.org/wiki/ICFP_Programming_Contest#Prize...).
- chii 5y ago> A bad Haskell programmer will get absolutely nowhere. that's a feature, not a bug in my books! Maintaining code written by other bad programmers is the bane of my life (despite getting paid to do it, so i can't complain).
- iainmerrick 5y agoHeh, there’s something in that. Now I’m wondering what the worst mainstream language is for maintaining somebody else’s legacy code. C can definitely be pretty bad... but I’m thinking maybe Perl?
- mmis1000 5y agoI think functional programming is less popular simply because people just aren't good at it. Functional programming is a good way to describe how a system works. By describe the input, the processing, the output. You describe the whole diagram. However, in reality. People just sucks at thinking systematically. It's likely that the whole education system never taught you about how to do it. Everyone was taught to do things step by step and smash the results together to see if it works instead of prepare everything before doing any actual work most of time. And if it actually need preparing, there is usually a pre made checklist for you to do it easily. That type of thinking process isn't that common in our daily live. And of course no one is used to it. But I think people should at least do it once. Even you are still programming interpretively afterwards. It could benefits you very much and make you a better programmer.
- jb_s 5y agoIt's been ages since I've used a "real" functional language but wouldn't it be nice to parameterise externalities like time, and have events occur "spontaneously" that force application state to update? Kind of like interrupts. Or now that I think about it... it sounds a bit like React where DOM events etc force application state to update (in a pure fashion) Has this been done before? So your LED function in pseudocode looks like ToggleLeds(leds, t): for each LED LED.power = (LED.start + 1s) > t ? ON : OFF And this is invoked from main() as follows main(): ToggleLeds(this.LEDs, Events.Time) Where Events.Time is some kind of event stream which allows the runtime to reevaluate main() and any other dependent functions each time it's updated edit: And to sidestep the obvious performance issue with the function being reevaluated every few microseconds :D you would implement something like this main(): ToggleLeds(this.LEDs, Events.Time(ms=1000))