All programming philosophies are about state
worldofbs.com
worldofbs.com
OO, functional, imperative, declarative: these are ways of controlling dispatch.
Monoliths and microservices are both ways to organize codebases and teams of programmers and control whether dispatch is intermediated by the network or not. Either way, both of these options are implemented by some kind of language in the previous category (OO, functional, imperative, or declarative).
Service-oriented architecture applies to both monoliths and microservices, and very few programmers still working in the industry have really seen what an alternative to service-oriented architecture actually looks like.
> One of the fundamental concepts to remember is that microservices architecture is a share-as-little-as-possible architecture pattern that places a heavy emphasis on the concept of a bounded context, whereas SOA is a share-as-much-as-possible architecture pattern that places heavy emphasis on abstraction and business functionality reuse. By understanding this fundamental concept—as well as the other characteristics, capabilities, and shortcomings of both micro‐ services and SOA that I discussed in this report—you can make a more informed decision about which architecture pattern is right for your situation.
Or is this a distinction you would not make (or were not even aware of)?
[1] Mark Richards - Microservices vs. Service-Oriented architecture
First OO programming is a specific style of imperative programming. OO is simply imperative programming with state and functions scoped into instances. If you are doing OO, you are also still doing imperative programming.
You can't really compare OO to functional because it would be like comparing a very specific concept to a very general concept. Like comparing cars and planes, but instead you're comparing a 2020 tesla with all planes in general. No... either compare specific cars with specific planes or planes in general with cars in general.
Declarative programming, on the other hand, can be stateless or stateful so it doesn't really apply here. Declarative programming is sort of left field to all these programming styles because technically chatGPT is declarative. Declarative programming is more about linguistics, AI and natural language processing. It's completely orthogonal to state management.
Functional programming and Imperative Programming are the two correct categories to compare here. They are siblings in the hierarchy and they are distinct and in essence simply two different ways of handling state.
Case in point: If you change one thing in your imperative programs. One thing... then your imperative program immediately becomes functional.
This thing is immutable state. Imperative programming with immutable state IS functional programming. Change the way you manage state, then you essentially change the name of your programming style.
The two paradigms are in essence two different styles of state management. All the other stuff with OO and Declarative is sort of fluff and distracts from the true essence of the isomorphism the author noticed here.
It's about their meaning (as used for decades) within the developer community at large, as established and commonly understood.
And in that, there's absolutely a functional vs OO dichotomy, the first meaning Lisp, Haskell, etc, and the latter meaning Java/C++/Smalltalk and such (doesn't even matter if Smalltalk for example has a different conceptual model for its OO or different dispatch mechanism, etc).
And sure, "well, actually Lisp has CLOS" -- but OO and functional as commonly used (and as the author uses it) means the part of functional that's about first class functions and immutable data and purity, and OO means Java/C++ style classes and coding style.
Ditto for "imperative", which in TFA just means "C style more direct manipulation of state", even if OO in say C++ is still imperative in the academic sense of the term (and, heck, even that is not that clear cut. "Procedural" programming for example is still imperative in its manipulation of data, but the terms have been used in academia and industry to describe different things. So it's not about a naive application of the definition, but rather about the intention behind the term).
>Declarative programming, on the other hand, can be stateless or stateful so it doesn't really apply here.
Again, for the purposes of TFA, it doesn't matter if declarative can be "stateless or stateful".
The author doesn't say that the programming language philosophies are about "different approaches to state across a single axis" (e.g. stateless vs stateful). He just says that they are about "different approaches to state" period.
In this case, regarding declarative programming, the difference is not "keeping state or not", but "the programming managing whether state is kept or not (and how)" vs "the language managing it and the programmer just declaring their intentions".
>This thing is immutable state. Imperative programming with immutable state IS functional programming.
Not in any colloquial use of the term. SSA form might be "functional programming", but it's not what 99% of devs (and the author) means by functional programming, which includes the trappings and idioms offered by traditional languages called functional programming languages.
No mistake made by me. The popular or even academic usage of the terms isn't relevant to the topic here. The reason is because the way these terms are used are highly inconsistent even in academia. They're not formally defined, they're just used for informal fuzzy communication.
I simply define a hierarchy here that's inline with our intuition to point out things the author and you completely missed.
I'm totally with you in that the popular usage is relevant in MOST cases. But in this specific context it doesn't work because the author attempts to do a very broad isomorphism across terms with fuzzy definitions stating that all of these styles are different forms of managing state and he misses the actual true meaning of what's going on.
Let's not get into language specifics like CLOS for lisp. Let's just focus on the core programming style without considering a specific language. All of these programming styles, more or less can imitate each other when managing state. None of them have a crystal clear way of handling state that's specific to their style. OOP can imitate Imperative and vice versa. In fact I'll list all the connections here:
OOP -> FP
OOP -> Imperative
OOP -> Declarative
Imperative -> OOP
Imperative -> FP
Imperative -> Declarative
Declarative -> OOP
Declarative -> FP
Declarative -> Imperative
An arrow represents "can manage state in the same way as".Basically almost every programming style listed here is so similar they can practically imitate each other. An isomorphism is obvious and the differences in state management are trivial. However, there is one exception above. You will note FP is not at the left side of the table.
Functional programming Cannot imitate ANY of the other paradigms. Both the author and you completely missed this.
What I am saying is this, there's really only two basic ways of handling state and these two ways are fully exemplified between functional and imperative styles.
All the other styles including the SOA stuff is just fluff. Different ways of doing the same thing. It's like replacing every letter in the alphabet with a different symbol and calling it a new language even though it's still more or less the alphabet.
>Not in any colloquial use of the term. SSA form might be "functional programming", but it's not what 99% of devs (and the author) means by functional programming, which includes the trappings and idioms offered by traditional languages called functional programming languages.
This statement here is categorically wrong even when considering colloquial understanding of THIS concept. It is just factually completely incorrect. Have you tried doing this? map, reduce, recursion, and all FP patterns become your ONLY tools when things become immutable.
Try writing fibonacci with everything immutable in say something simple like JS or python. Good luck doing it without using recursion or reduce.
The isomorphism between FP and imperative programs that use immutable variables is something well known, but apparently, not by you.
SSA, no recursion, no reduce ;)
def factorial(X: int) -> int:
encode = lambda n: ((lambda N: lambda g: lambda f: lambda x: x if N == 0 else f(g(N-1)(g)(f)(x)))(n)
(lambda N: lambda g: lambda f: lambda x: x if N == 0 else f(g(N-1)(g)(f)(x))))
decode = lambda f: f(lambda x: x + 1)(0)
fact = (lambda f: lambda n: cond(vaut0(n))(lambda _: un)(lambda _: mult(n)(f(f)(pred(n)))))(
(lambda f: lambda n: cond(vaut0(n))(lambda _: un)(lambda _: mult(n)(f(f)(pred(n))))))(n)
return decode(fact(encode(X)))
Of course, I'm joking, as the Y combinator is clearly visible here.Full form for those interested : https://termbin.com/cd3q
This snippet was clearly functional .
And the point is that this doesn't matter. It's not about C++ OOP being able to "imitate imperative" or not, it's about what C++ (or OOP in general) adds to the table regarding how programmers should think and hide state.
>All the other styles including the SOA stuff is just fluff. Different ways of doing the same thing.
Like above, you're still reasoning from lower levels of abstraction to point the similarities and underlying unity, whereas TFA is all about the level of working with each language and its "programming paradigm" as a programmer.
It's like you're arguing that "in the end, chemistry is just physics". Or worse, "in the end, cooking is just physics". Sure, but that is pedantically irrelevant information in a regular cookbook or when talking about national cuisines.
>Try writing fibonacci with everything immutable in say something simple like JS or python. Good luck doing it without using recursion or reduce. The isomorphism between FP and imperative programs that use immutable variables is something well known, but apparently, not by you.
My whole point was that it's not about the isomorphism, but the higher level trappings. Funny how you've managed to miss the whole argument, it's not even like I didn't spell it out (or like I haven't already said that SSA is nominally "functional programming") but it doesn't matter
That's your point. And MY POINT is that your point is missing the true relationship between these programming styles. C++ is not a topic here. That is a specific implementation of a programming style. We are talking about programming paradigms, not specific languages.
Additionally How is anything added to the table if every language can more or less imitate one another? Obviously you're referring to a bias that a language sort of pushes you toward. This is what YOU are saying. What I am saying is that YOUR perspective, again, is the one missing MY point.
Let me put it to you this way. When you notice everything is more or less isomorphic then you notice a true difference. FP is fundamentally different. Imperative and FP styles are MORE different then all the other styles of state management compared. Then from this you can see that there's a missing hierarchy within all these programming language paradigms. This is what I am pointing out. I'm presenting flaws of the original point, and making a new point.
>It's like you're arguing that "in the end, chemistry is just physics". Or worse, "in the end, cooking is just physics". Sure, but that is pedantically irrelevant information in a regular cookbook or when talking about national cuisines.
No I'm not. I'm saying that YOUR and the OP's point is wrong because the "everything is the same" result is what you get when you compare programming paradigms this way. You need a hierarchy and only sibling nodes in the hierarchy can be compared.
>My whole point was that it's not about the isomorphism, but the higher level trappings. Funny how you've managed to miss the whole argument, it's not even like I didn't spell it out (or like I haven't already said that SSA is nominally "functional programming") but it doesn't matter
In logic and in programming as you get more and more higher level things become more and more isomorphic until finally at the highest level of abstraction everything is the same thing. This follows logic.
In programming and in nature as you get more and more lower level things ALSO tend to become the same. But this does not follow logic. From our observations everything looks to be made out of atoms, and by design all programming styles compile into assembly instructions. But this doesn't follow any form of logic; it doesn't HAVE to be this way by logic, it is simply this way by observation or by design.
That being said, your comment about higher level trappings make no sense. Going in EITHER direction things should become more and more samey. Additionally I'm not even going to a higher level or a lower level. I am simply saying these programming paradigms have hierarchy. I am also saying within each paradigm, things are so flexible that there is no specific way of managing state. You can manage state the EXACT same way in all paradigms, except ONE style. By seeing it this way a clear hierarchy emerges. That is it.
SSA has nothing to do with this. This is a term from compiler design and you're accusing me of going to a lower level? Still I get your point and in SSA from the way you use it is not "nominally" functional programming. IT IS functional programming. Literally look at the other persons post who implemented fib. That's totally functional.
One of the tricks of making compilers is transforming the code into whatever form makes the thing you're trying to do easier. SSA is one of those forms, and it means that each variable has exactly one assignment. There is some "cheating" (phi nodes) involved to handle things like "if (...) { a = 0 } else { a = 1 }".
Notably, LLVM IR is SSA.
Since there's no mutation, it's technically a functional language.
SOA uses message passing only as the synch mechanism, but sharing memory allows for other strategies with synch primitives such as semaphores or mutexes (with the assumption that collaborating processes run on the same physical machine, or communicate through Remote Procedure Calls).
http://www.composingprograms.com/pages/48-parallel-computing...
Not really. They are orthogonal to dispatch, which is why you can have different dispatch strategies with all (or at least most) of them. Dispatch is an implementation detail.
In what industry? I'd agree that anyone making anything web-facing is using some form of SOA, but there are other things, too. Desktop apps (and to some extent mobile apps that aren't just a thin interface over a web API) still exist.
Unless you're arguing the maximalist approach, i.e., that anything with an API that tries to hide any form of implementation details is an example of SOA. In which case, that's not very interesting...
It is really about state, dispatch is an implementation detail and can be simulated in many languages.
The main difference between monoliths and microservices is in coherent and, if possible, atomic changes in state of different subsystems. Monoliths allow use of blocking primitives or atomic transactions in software transactional memory to achieve that, to do so with microservices is much harder.
The very existence of many implementations of database systems is an attestation that many, if not most, software systems require consistent and atomic transactional processing with regard to state.
State is central to all software.
With what dispatch method to achieve coherency in the processing of state changes is an implementation detail.
I've seen people set up a microservice architecture that was really a distributed monolith because multiple services were reading/writing from the same database table at the same time. I've also seen them use a central locking service as part of that.
That just shows that it's possible to carry forward the state management practices of a monolith into microservices. State management is not the differentiator.
You definitely can decouple teams to enable them to move independently in their software development lifecycles with monoliths too. Our team definitely do that.
And in realm of programming langages, the state management is a differentiator. For what it worth, the very difference between Turing machine and lambda calculus is the ability of computer (a human computer at the time) to rewrite some part of state when using rules of Turing machine.
Again, for what it worth, when you have arbitrarily modifiable state not all things are possible: https://www.mail-archive.com/haskell-cafe@haskell.org/msg797...
Two problems with this statement:
1. imperative & declarative are about dispatch, OO & functional are about much more (dispatch being one of the most negligible components)
2. dispatch is extremely concerned with state: e.g. for declarative, state is handled by the "dispatcher", whereas for imperative state is an extra responsibility of core logic - the approaches to state handling is one of the most important differentiators between these paradigms.
As for your 2nd & 3rd paragraphs, 100% agree but they don't seem to contradict the article so I'm not sure what point you're making.
OOP: OK, I mostly had enough fun, can we try to tame the bit-flipping chaos with real-world analogies, without deflating all the fun?
Functional: Any Monad is by definition an Endofunctor, which also means it's an object in the category of Endofunctors, where the monadic μ(flatMap) and η(unit) operators satisfy the definition of a Monoid in that particular Monoidal Category. You got that?
Declarative: I'm gonna need a corner office with a view, and $200K/y.
Eg. Clojure is a modern functional language that is used for a lot of things & happens to be dynamic.
I think it is not precisely delineated along the static vs dynamic sides: In addition to Elixir/Erlang, and Clojure we have eg F# and Elm from the static side in the pragmatic team.
Elm is very much pure, yet it's on the pragmatic side: it forgoes certain powerful abstractions (higher kinded polymorphism, type classes), which enables the compiler to give very helpful error messages, and makes the language easier to learn.
Huh… you’re quite right. I always thought it was impure. In that case I retract my previous comment (pity I can’t edit it any more!).
OOP is about encapsulation of data and algorithms which deal with that data in logical units (objects) and allowing certain operations eg inheritance, polymorphism (sometimes) etc over those objects. It can be declarative or imperative.
Let’s treat data as what it is so I can sleep at night again.
At it's core, it's about treating data like data and not about having to much about with "objects".
Some blog posts are wankery. Haskell in practice is as separate and separable from it as you want.
I learned what a mathematical monad is out of interest (after using them in practice for years), and my conclusion is that it was a huge waste of time with close to zero use for my programming in Haskell.
Agreed. I regret that I tried to learn what a mathematical monad was before using them, out of a belief that it would help me get started quicker with Haskell. It didn't. It made something confusing that's actually pretty simple.
The whole "a monad is a monoid in the category of endofunctors, what's the big deal?"-in-a-condescending-tone bit comes from "A Brief, Incomplete, and Mostly Wrong History of Programming Languages": a satirical blog post from 2009 (http://james-iry.blogspot.com/2009/05/brief-incomplete-and-m...). It is not indicative of the general community, but the joke has been reproduced enough that people who don't know anything will encounter it, think the practitioners are serious about it, and write off the whole thing as unwelcoming. It's a bummer.
You can’t have exclusive language and then be surprised when nobody wants to worship at the altar of your obfuscation.
ETA: Look. I was reading 25 year old complaints about why Lisp isn’t popular 25 years ago. You can go on scratching your heads for another 25 years or you can start taking criticism instead of deflecting.
Is this... directed at me? Are you saying that I've been guilty of this behavior? Could you point me to an example? Because I put a lot of effort into helping the functional programming community be more welcoming, so it'd be important to me to know if I've gone astray somewhere.
> It would help if functional didn’t collect all the people with their heads all the way up their own asses.
I don't disagree that the kind of people who like to be particularly condescending tend to also end up in this community.
That said, I will point out: (a) there are still plenty of assholes in non-functional (or even anti-functional) communities, and (b) the community has grown a lot over the years, and has mostly shifted to being much more positive and inclusive. Unfortunately, the reputation from decades ago is still strong, and I honestly believe part of that is due to historically-justified propagation from people like you in this comment and the other person in the comment to which I originally replied, to name two examples. There's a lot of residual hostility against the FP community that I don't think is truthfully justifiable anymore.
Mind you, this “state management” thing and the fuss around it, which pops up for last ten years more actively, was never a concern that I found particularly useful to have a name for. 20+ years of a mediocre career and I still don’t get it, neither why, nor what the problem is. Maybe that’s why it is mediocre? Living in a bs country doesn’t help either. Otoh, I can make things work and ship mvps next week once there’s a plan and determination. (No, it’s not PHP.)
I believe that the fuss part comes from the fact that software becomes more and more low-level uncontrollably, so there’s a lot of self-imposed state that business isn’t even aware of and which becomes per-LoC routine that is easy to stumble upon but hard to document or explain to a person outside. State belongs to business and is not in your control, all other state is parasite. Mapping business state 1:1 in your program keeps everything simple to do, to change, to grow. It’s usually imperative, sometimes declarative (that’s where programming emerges) and never functional. Parasitic state’s place is under a rug. Library, syntactic sugar, framework, db/service, platform, whatever.
To conclude, well, I have nothing to say really. Still confusing.
Controlling "state" amounts to trying to force the world to match the text of your program as closely as possible, so you don't go wrong.
There are quite a bunch of things that are _hard_ (IMO) and related to state management a non-exclusive list of examples:
- GUIs in general, the more interactive they are, the more state you need to manage.
- Caching (at any level) is a form of state. You need to be aware of how and when data changes and who changes it and which parts of the caching are affected, when you care about it etc.
- Memory and disk allocation are state. You need to be aware of what your resources are and when to free them up. You might use locks to make sure your data doesn't get corrupted. At a higher layer you might have authorization models to restrict access.
- TCP is state. What do you do when connections fail or get interrupted? Do you store/buffer messages to be processed at a later time? What does that mean for the receiver?
- SQL databases are stateful. Do you ever need to know what happened when? How do you restore previous state? How do you make that efficient? Are backups/snapshots good enough or can you leverage something more granular?
As others noted, the size of my projects may skew the perspective. But I’m not sure how big something should be (and tightly coupled) to start worrying about it, unless my primitives aren’t too great to use without a second thought. What bugs me even more us that popular libraries don’t even provide them out of box, suggesting to write code and to spread structures in a special convoluted way instead.
Programming philosophies encompass much more than just state management, and narrowing the focus to state doesn't seem particularly enlightening to me, at least with these summaries. I'd love to be proved wrong, though.
However, I definitely agree that it's a stretch to say that architectural patterns like monolith vs microservices are about state. Those have far more to do with structuring deployments and organizing work.
This is not a deliberately reductive take, I just can't think of any other meaning.
It wasn't a large leap conceptually, but the mental model change came from a long lost hacker news comment along similar lines.
I don't think it is a reductive take. The second instance of the term here instead involves the more traditional concept
program: "a regular plan of action in any undertaking" https://www.dictionary.net/program
Programming (in the sense of software construction) paradigms, philosophies, indeed "languages" etc., are about regularizing plans of action for complex computational undertakings.. usually.
It feels secret, because no one really talks directly about how all programming paradigms are about state. But once you realize it, you can start to see how a lot of high-level programmers reference it when they talk, all the time. Just something that "everybody knows", once you spend enough time working in code.
Personally, it wasn't the realization that it's all about state that was particularly exciting. It was using that new interpretation to understand how I could mix and match different types within a single project, to make each separable/modular piece of it, rather than dogmatically sticking to a single paradigm throughout. I think there's a definite mental breakthrough when a programmer can confidently use multiple different state paradigms to handle what would otherwise be a clunky or unwieldy implementation in a single paradigm.
> Functional - Modifying state is hard to get correct; so let's focus on the rest of the problem (the part we can get right!)
Pushing state to the edges is actually just a consequence of this impulse.
In the end it isn't a satisfying solution, because you end up with every bit of low-level, inconsequential state percolating up to the top and polluting every single data structure along the way.
I feel there must be a better approach ... in which the "impertinent" state can be abstracted away and managed orthogonally to the functional description of the program.
Originally Riak shipped its data around as part of Erlang’s standard messaging mechanism, the process mailboxes. Over time that was recognized as a bottleneck and data started being handled differently (honestly it’s been such a long time I don’t remember the details, or how far down that road we went).
I have no conclusion to this ill-formed rumination, I’m afraid, just that your point about managing state independently reminded me of that conversation.
Edit: please donate the proceeds of your bet loss to victims of the earthquakes in Türkiye.
To people on the right it’s just completely naive. The first half is mathematically impossible and the second half is so wishy washy that it leaves room for everyone to claim they were born further back in one aspect or another on the proverbial board.
Because generosity is exploitable, it's better to exploit.
Politics is really just dividing up the shared resources, which is the root of that joke. Who cares about politics and doesn’t care about the resources part?
I guess after 30 people forget how to share per that quote. Never happened to me though. Personally I don’t even think it’s accurate, in my life I’ve never seen people swing from generous to… let’s just say “not”
The people who were always like that a little got more so.
That's top left on the political compass... Is that not a thing in the US?
Not caring at all is certainly a choice I can see individuals making, but someone who is politically engaged and doesn't care about the resources, I can't even fathom it.
You can tell online people isn't representative because they think it's a joke when this is actually pretty common among average people. (Who aren't on a single position on the political compass but are instead "cross-pressured".)
The point is not to force generosity on others. The point is to fight back against unjustified value extraction by the powerful and to democratize the workplace.
Socialists see charity as trying to patch over symptoms of an inherently deeper problem. It’s not a philanthropic movement. It’s a worker movement.
I guess everyone familiar with US history should find the quote to be utter bullshit, then.
The American Revolution, Civil War, Emancipation Proclamation, 19th Amendment, Great Depression, WWII, and Civil Rights Act were all major upheavals in society that brought on rapid change.
A lot of people died prematurely, but a lot of lives were later extended or saved to balance that out somewhat, and the system surely didn't collapse.
When I was younger I was more confident that the world and institutions could easily be remade or replaced just by reasoning about them intelligently, and I was also more confident that everyone's interests and morality roughly converged, so not very much had to be done to allow us to get along and live together in harmony. (Nonetheless, I think it's likely that you'd have considered my adolescent beliefs to already be right-wing and to be lacking a consciousness of political theory.) Now I'm more apt to think there are things that we can't redesign, things we depend on that evolved with no one designing them, and lots of immutable constraints from human nature. Also that a great deal of the harmony we experience is fragile, and we might be much worse off without it.
(For what it's worth, I would also try to avoid making generalizations about people that imply what they're supposed to believe based on their age -- or making fun of people on the basis that their beliefs are supposedly out of step with their age.)
In my 20s I bought a house, started filling a brokerage account, started managing people -- so I certainly started to feel those incentives, but I am both principled enough to not act entirely out of self interest and wise enough to realize that I would have to be considerably wealthier before the capital-side incentives actually overtook the labor-side incentives in their importance to my bottom line.
Unfortunately, even these meager principles and this sliver of wisdom are rare enough that "temporarily embarrassed millionaires" abound.
I think societies, states and economies are emergent properties of people living together. But the more I know people, the less I believe overarching systems, models and grand narratives.
They are all wrong. Not only in respect of what should be. We can’t even agree on what is or has been.
That sword cuts both ways.
The more you see of the world, the more you treasure what the current ideology has brought.
One response is to say we need to empower the State with more authority to regulate those problems, with the hope that it doesn't just cause even more problems.
Another response is to say we need less State authority, and find other ways of dealing with certain problems typically given to the State. Not every problem is a nail to hammer with the State.
Neither response is correct in all circumstances.
I don’t want to get more into that here because there be many dragons. But I’m easy to contact if you want :)
Also, it would make an epic t-shirt in the hands of someone with some actual artistic skills. Is there such as a thing as missing a cool t-shirt you haven't seen? :/
"Down with the State", "Shrink the State", "Less State -> More Good", "No State, No Problems", "Spread the State", "No State/Stay Pure"? Oh my kingdom for a programming-literate copywriter.
Hypothesis: All of software *is* about change management ----
It dawned on me that all the software design efforts - Design patterns, programming styles, management practices - are all geared towards one thing at its core - managing change. For example:
* Command pattern: Figure out all the things that can be called and instead of a giant if/switch, look them up by name and call them. That way when you need to *change* the list of things you want to call, nothing else is affected.
* Structured programming: Keep the common code in one place and call them from many places. That way when the common code *changes*, we can contain the change to one place
* Object Oriented Programming: Keep the data and behavior common to one actor in the system contained in an object/class so that when it *changes* we have to change only that thing.
* Functional programming: *Changing* data is bad. Instead create copies of data and change them.
* Scrum: It's hard to predict too far into the future because things *change*. So let's try to plan for just the next n weeks.
I would also add that statefulness is incredibly hard and complex to get right in complex applications and pure fp languages like haskell lack enough emphasis on runtimes to handle this gracefully.
All the state is in the function stack and the instruction pointer.
FP is all about side effects. Every time you declare something you're effecting a compile time side effect. And then there's monads, which are literally reinventing state with a worse API. I find it odd that no mainstream functional language has an explicit call stack monad and an explicit symbol table monad. Maybe because even the "purists" realize how asinine that would be.
> I would also add that statefulness is incredibly hard and complex to get right in complex applications and pure fp languages like haskell lack enough emphasis on runtimes to handle this gracefully.
Mutable state is a powerful tool. And it's true that if carpenters used table saws with the same level of discipline that programmers use compilers, there wouldn't be a carpenter left with any fingers.
Nevertheless, powerful tools can be used in a responsible and disciplined way and if you do you can be highly confident no unexpected dismemberment, literal or metaphorical, will occur.
Edit: to be more explicit, the mathematicians who invented Algol were well aware of all the theory for so-called functional programming and rejected it in favor of treating programs as transformations of a multidimensional space with good reason.
Every programming language is about side effects.
Computations that don't lead to side effects are useless.
That being said fp splits a core functional part (pure, no side effects of any kind) and the interpreter part where the instructions are run.
The way one should visualize pure functional programming is that functional programs don't _do_ anything. They are equivalent to producing a description of the operation. The actual execution happens when those descriptions (generally encoded in effects or data types such as IO/Task) are interpreted by an external runtime.
> Computations that don't lead to side effects are useless.
Right.
> The way one should visualize pure functional programming is that functional programs don't _do_ anything. They are equivalent to producing a description of the operation. The actual execution happens when those descriptions (generally encoded in effects or data types such as IO/Task) are interpreted by an external runtime.
This is the right way to understand imperative programming too. The program really doesn't do anything, it just describes a set of possible processes. Then the runtime converts the program into one of those processes (or maybe more than one). Each of those processes is a path through a program state space graph, but supposing your process is to have visual effects at various nodes in that graph the runtime will do externally visible things. It's really not any different.
In the end FP is basically just a way to force programming in a SSA style, plus whatever silliness the designers go for to deal with the reality that mathematical functions can't really capture input and output in any sort of useful way. Interestingly, it appears to me that in pure math the vernacular text around the equations fulfills that role.
2. The mix of concepts between programming paradigms, architectural design, etc. doesn't body well supporting the conclusion about "every programming philosophy"
It feels to me like much ADO about nothing
Also I wonder if algebraic thinking is not different from state, since you map / combine subsets of the domain[0] with more interesting operations. You don't got from 2->3->4, you can (+ 2 2).
[0] which some would say, is state, but here it's reified as a standalone value
"whitehead and Hickey" search links back to this comment and only one other thing that can't be relevant.
So, WTF are you talking about that we should know it and search engines don't?
https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
The library itself isn't too important here, but its website cites the philosophy of Rich Hickey and Alfred North Whitehead on state being an illusion:
Rich Hickey has spoken eloquently on mutable state in his talk "Are We There Yet?" [1]. To summarize, Rich and Alfred North Whitehead [2] don't believe in mutable state, it's an illusion. Rather, there are only successions of causally-linked immutable values, and time is derived from the perception of these successions. Causally-linked means the future is a function of the past; processes apply pure functions to immutable values to derive new immutable values, and we assign identity to these chains of values, and perceive change where there is none.
Hickey's talk itself is quite interesting and goes into detail on the relevance of Whitehead's ideas in his book Process and Reality to concurrent programming.
[1] https://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hi...
[2] https://archive.org/details/AlfredNorthWhiteheadProcessAndRe...
As mentioned elsewhere: Pure functional programming (as in lambda calculus and its friends) does not consider state. It consider expressions that need to be reduced – the user can on top of that build something that mimics state, which is entirely up the programmer.
However, I think the most productive way of assessing programming languages for real world software applications is by looking at state management, as it usually turns out to be the most complex part of that application.
f(g(h(x)))
It's a mutable and invisible data structure that drives the execution of the program.Lisp stands in stark contrast to this. The lisp philosophy is this: as with every science, you mould your language to your problem, not the other way around.
It's like saying using chatGPT to write your code is just a form of state management. Technically yes, but this viewpoint is overly pedantic.
Declarative programming is therefore more then just state management.
However, the existence of Universal Turing Machine (a Turing Machine that can simulate behavior of any other Turing Machine) demonstrates that data can also represent state - i.e. you can encode an arbitrary Turing Machine's state machine in the tape, without changing the Universal Turing Machine's state machine at all. That's how we got stored program computers.
In a more practical sense, a useful distinction between data and state is within the boundaries of the program - any data that is internal to your program can be considered state, while any data that you read from an external source/write to an external sink can be considered just plain data. Separating the two is a helpful technique in writing reliable software.
My main beef with functional programming is that you essentially put the state in the "instruction pointer" and its history of walking the call stack instead of bound to symbols. It does not fit my way of thought. I personally prefer more or less statemachines.
Basically how FP sells:
def a
1
end
def b
2
end
def c
a + b
end
You see? c = 3! Now try to apply this logic to (real world with effects): def a
(conn: DBConn) => { .... some complex code ... }
end
def b
(conn: DBConn) => { .. another complex code ... }
end
def c
(conn: DBConn) => a(conn) + b(conn)
end
So c = ? Still transparent?We’re talking about something that starts with things like reduce vs. a for loop with mutable variables to accumulate values. Once the language forces you to think in terms of stateful operations to perform even basic tasks, the reflexive tendency is to use mutable state to perform any task. At that point, nobody really knows what a functional does because you have to understand the entire system to know what the output of a function will be.
Besides, it's good practice to limit how widespread side effects are in any program, anyway.
Monads is a nice way to hide state I have to admit, but not because of referential transparency and "reasoning".
Each Haskell monad might have a different implementation of the >>= (bind) operator, which is referentially transparent and which you can easily substitute. The IO monad would be an exception: I believe that one is not referentially transparent and thus cannot be substituted, because it's not implemented in Haskell. You can however imagine that it is a sort of state monad for the external world, but that's a fiction in practice.
(Note: Haskell monads are not "real monads" from category theory, and nor are monads in any other programming language AFAIK... So it's helpful to be very specific about what we're talking about when we use the word "monad" without context. You can implement a monad-like thing using classes in Python or many other languages, but those aren't referentially transparent and aren't typically the monads people are talking about in the context of pure functional programming.)
No, it's not. and a and b are neither in your second example, otherwise they could not produce any result that could be combined with +. Unless `{ .... some complex code ... }` is just `return 42`.
Referential transparency means that we can evaluate the same thing over and over again and getting exactly the same value back. Obviously that does not work with what you have constructed.
It helps when you annotate your examples with return types. That will make things clear very quickly.
In functional programming, we think of computation as reducing an expression; a computation is complete when it can't be reduced anymore. In imperative programming, we think of computation as updating a store of data; a computation is complete when we run out of steps to perform.
When you're partway through execution, the "rest of the program" in an imperative system is captured by the current state of the store (including the stack). In a functional program, the "rest of the program" is just the program expression itself, reduced as it has been up to this point.
I tend to like the functional model, myself, but I can see the mental appeal of keeping the program text fixed, and isolating all changes to a separate store of data.
Having a good representation / classification of alternative choices and tradeoffs could help people with more informed design choices and selection of tools
Also, you forgot Data Oriented Design: state should be modelled to match how the hardware.
1. Language Oriented
2. Relational (the main thing there is largely about reversibility)
3. Procedural: Control flow is way more important here than state. Strict procedural might doesn’t allow early return since you then have a block with more than one exit.
Therefore, procedural and language oriented seem more about control flow.
You can try to ignore state but at the end of the day, you still want to do something with it at the start and at the end, at least one of the two.
Try to derive it from the data aka the environment.
Rather prefer adding state to the environment (timestamps, recipesteps of a product), then keeping seperate track from it, because seperation is, additional state.
And state is a curse:re. So dont.
If it cant be avoided, dont duplicate state, add a machine that resets it upon sensors detecting a changed environment (data) and revert wherever you can back to deriving it from data.
If it is unavoidable, make it concise, so that at least the maintainers can follow the state and see were it goes wrong.
But the philosophy of p. is also dealing with ways of creating abstractions, because you can't view everything exclusively on a very low level of abstraction, without loosing your mind.
That’s assuming that „declarative“ means „describing outcomes rather than describing process“.
The old React is not 100% functional (partially OO), but it still is declarative.
(Edited for clarity)
When a bird population starts speciating along, say, the arctic circle, you have a line of a bunch of closely related sub-species that can interbreed with the sub-species nearest them.
Sub-species 1 ... Sub-species 2 ... Sub-species 3 ... Sub-species 4 ...
1 can breed with 2. 2 can breed with 1 and 3. 2 can't breed with 4 because they are too far apart, but if a 4 came to the territory of a 2 they could breed. Etcetera.
Eventually You get a whole circle of interbreedable sub-species as they spread across a circle of arctic latitude around the world:
1 ... 2 ... 3 ... 4 ... 5 ... 6 ... 7 ... 8 ... 9 ... 1
But once you get to that point you find that sub-species 9 and sub-species 1 can't, or won't, interbreed anymore. They are now two entirely separate species despite coming from the same origin and living right next to each other.
However none of them can interbreed with a complete separate lineage, such as wolves.
TLDR: The overlap is before the extremes, not between them.
Look up Graham Hutton’s writings on relations. He has published quite a bit about this, and he knows a thing or two about functional programming.
Edit: I mean it’s called „Functional components“, are they lying?
React (can) have referential transparency and up to a degree immutability (yes hooks are not side effect free, but so isn’t the IO monad).
Code carries data in the same way that electrical circuits carry electricity.
Frequently, there are 30+ local variables inside a procedure or a function. It is very rare to even see a class definition. I am talking about mostly financial logic.
For example, if I have a `String` whose byte sequences must be valid UTF8 (as in the case of Rust's `String` type), or if I have a `UnitVector3` (which is a `Vector3` that must be of unit length), then allowing direct manipulation of structure fields (X, Y, Z) creates the possibility of instances existing that break the invariant. In the case of Rust `String` that leads to memory unsafety.
Abstractions make it possible to enforce invariants using the type system. If `UnitVector3` provides functions such as a constructor that either (1) takes a valid unit vector as input, or else fails or (2) takes any vector as input, and changes its length to become a unit vector (etc.); and also all of the type's functions return `UnitVector3` (where appropriate, e.g. transformations such as rotation), then it is impossible for an invalid `UnitVector3` to exist!
To continue the example, if I add two vectors together, then their sum will be a `Vector3`, not a `UnitVector3` -- so that operation should have the appropriate return type (`Vector3`). Meanwhile rotating a `UnitVector3` will always return a `UnitVector3`. But translating a `UnitVector3` yields a `Vector3`. And so on. Any code written using these operations can determine from their return type what scenarios it needs to handle (is the return value guaranteed to be another unit vector, or could it be any vector?).
If any code can access and manipulate the X, Y, Z coordinates of `UnitVector3`, then any code that relies upon instances being actual unit vectors has the potential to misbehave. That code is correct -- it's the code that created an invalid `UnitVector3` that's wrong! But the error will show up somewhere else at runtime.
Encapsulating these fields within a type (that provides getter methods), and provides a constructor that validates the input (and fails on invalid), makes this entire class of error impossible.
It is still possible for code to attempt to construct an invalid `UnitVector3`, but the call to the constructor (which validates its input) will fail, thus causing the program to fail as soon as possible, and in the most relevant place!: In the code that's creating an invalid vector -- not some obscure other part of the program that is processing the invalid `UnitVector3`.
Using the type system to enforce soundness properties is impractical to achieve without encapsulation, and the ability to hide data and provide interfaces to it. Structures with public fields, where any code can modify their value, is very high risk by comparison.
I guess the author used "philosophies" because they are mixing pears and apples, so the most specific word that can be stretched enough to encompass those concepts is "philosophy".
on edit: yeah I guess cache invalidation is state too.
If the functional paradigm tends towards avoiding state, then it would seem to me that forth does the opposite: making almost everything a part of the state of a program that a developer can access and manipulate, even things that most structured programming languages do not expose to the developer.
[1] https://github.com/xoreaxeaxeax/movfuscator
[2] https://harrisonwl.github.io/assets/courses/malware/spring20...
Also: Any assembly language that predates memory protection.
Having said that, the leanest system will be reference types under the hood, as for example, the C# stack frame is limited to 1mb and can't contain your app's entire state.
So we arrive at today's cutting edge which is F#, Clojure, etc. that are functional wrappers around heap-based lookups.
Evaluating "(1 + 2) * 3" requires some state in the CPU, but I can treat it mentally as just an expression that be reduced down to 9 without me feeling like there's state involved.
The only thing that matters in my opinion is if it affects our ability to _reason_ about it
Many bugs arise from unexpected context: programmer writes code expecting one context but gets an oddly-shaped context they weren't planning for (the other bugs are typos, where the context is correct but your code is not what they planned it to be, or when downstream code has different behavior than they expected. Ultimately, the code is not doing what they expected it to).
But context is inevitable. Even "stateless" programs like compilers, which take an input and return an output, have a lot of context (e.g. scopes, prior analyses, virtual pc). In pure languages like Haskell this gets abstracted by monads (and not just the `State` monad, all of them); if you do not abstract the context you get a function with an obnoxious amount of parameters and return values which is even more unweidly.
When people say "managing state" what they really mean is managing context, so that you guarantee when a function is called it has the context (state and parameters) it was handled for. The way to do this is to make the context reliable, whether that be state invariants, strongly-typed parameters, less state/parameters, exhaustive case analysis, etc.
Bugs arise from incorrect assumptions. These can be assumptions about state or context, but really it can be any kind of assumption. Assumptions are a kind of dependency. Minimizing dependencies also helps to minimize assumptions. Similarly, statically checked type systems help by having the compiler double-check at least some of the developer’s assumptions. The more powerful the type system, the more assumptions can be expressed and hence checked.
OOP tries to minimize the assumptions and dependencies between code by encapsulation/data hiding and interface contracts. FP tries to minimize them by having the result of a function only depend on its parameters. Declarative programming tries to minimize them by having the program only describe relationships between inputs and outputs.
How to minimize the assumptions a given piece of code has to make, and minimizing its dependencies, are the bedrock of all software engineering advice, IMO.
Would this be a case of managing the complexity of the developer's systems model?
Abstractions in effect clamp the complexity presented to the programmer & may include assumptions about the given piece of code. Question being are the cognitive assumptions presented to the programmer able to result in unintended consequences, how often, & how severe in systemic terms?