5 ms·
That’s the whole point of functional programming: composition of small things to make bigger things.
by chrischen 6y ago
That’s the whole point of functional programming: composition of small things to make bigger things.
- munk-a 6y agoFunctional programming does it better - but it still suffers from the issue that when developing a programming solution we developers need to account for all edge cases (if we're doing it right) which requires a lot of decisions to be made. The important decisions for the use of the module will be carefully made - the less important decisions will be arbitrarily made. Almost no uses of the module will encompass a need for every edge case to be decided in a specific direction but when that module is reused the new consumer will probably have a slightly different requirement about which decisions go which ways - this, I think, is the central pain point of software reuse. Completely agnostic problems do exist and modules to solve those can be very strong - but that is a small subset of all the problems we want modules for.
- T-R 6y agoCareful decisions can scale with abstractions, if they're made with some rigor. In Python, for example, whenever I'm doing operations on lists, I need to consider every function in isolation - will this break for an empty list (zip), or will it break when given a generator (anything that traverses twice)? If I pass a callback with side effects, how will it behave differently if someone calls it twice? In a a more rigorous ecosystem, I can instead only worry about edge cases on classes of functions/data types - In Haskell, when doing the same operations, I can generally assume that traversal functions were written with consideration for functor/foldable/traversable laws, and those laws tell me how I can safely compose or swap them out without breaking expectations around those edge cases. Higher Kinded Types let code be constrained to work strictly with those abstractions, so I mostly just need to worry about "does this typecheck" and, very rarely, "is this an unlawful traversal". With things like linear and dependent types entering this mix, library authors can communicate even more subtle expectations at compile time instead of leaving users to try to anticipate them or find them at runtime - when you do need to explicitly consider edge cases, the turnaround time on discovering them is important.
- munk-a 6y agoI don't disagree - these sorts of questions can be worked out to an academic degree with rigorous proofs. Databases and data guarantees around them are very widely applicable and have been isolated from business logic in modern applications due to the heavy complexity of that logic - once upon a time it was common for applications to "roll their own database" i.e. build some custom data storage approach that worked well enough (hopefully) for their purposes. I worked on a MUD based off a legacy that began in the 80s that used flat-file storage as a primary data storage method and race conditions abounded. However, databases are cost effective to build and distribute, but implementing that level of rigorous proofs for your e-commerce app is not only generally undesired but often increases the difficulty in reaching profitability and can sink a company. Working on system development isn't limited to academic concerns, if you're guiding project development you have to make really hard calls about correctness vs. cost and I say this as someone who likes to be a perfection and do things the right way whenever possible. So I don't disagree that careful decisions can scale with abstractions - it isn't the case that it is impossible to build a well designed large system, but it will be expensive and at the end of the day we all (probably?) need to balance cost of investment vs. value of that investment - or are having someone higher up the chain making that decision for us.
- chrischen 6y agoI think learning to program for correctness has a cost, but it's a skill that stays with you. Once you know how to properly model things in a composable way you can definitely work just as fast or even faster as when you gave no care for correctness. And frankly, shipping an incorrect program isn't really necessarily a worthy tradeoff as you are simply deferring problems to the user or to later down the line. And you don't need rigorous proofs to enjoy the correctness protections from strongly typed languages like Haskell. The language does that for you, you just have to learn to model in them which is a one time thing.
- agentultra 6y agoI didn't need an academic degree to start working in Haskell and learn abstractions such as "Traversal" or any of the others. Most of them are painfully simple compared to the impression one gets from their names and the jargon that comes with the territory. However the benefits of sticking with those names and jargon is that it only needs to be explained once. Analogies and metaphors help when you're starting out but you don't need to keep them around. It makes speaking precisely with colleagues in the ecosystem easier. Proofs are on another level. I've also written proofs and they require much more rigor than you will find in Haskell. It can still be done without a degree but I agree -- not useful for most line-of-business applications. However the power-to-weight ratio of learning the abstractions available in Haskell is smooth. It takes effort to acquire but it pays off in spades. That's where the "scale" comes from. Polymorphism combined with composition means that once I know a type implements "Traversal" I can use the entire language of traversals with those data types without knowing anything else about the value I'm working with. The more things that implement traversal the more rich my program becomes.
- chrischen 6y agoYes but that's what composition solves. If the module as a whole cannot be used, you can just recompose the parts. As long as you've made clearly defined modules/functions all the way down with clearly defined inputs and outputs.
- gmfawcett 6y agoTo be fair, that was also the major point of OO, and of structured programming before that.
- chrischen 6y agoCould you elaborate?
- gmfawcett 6y agoI'll go one further. Can you think of a single programming paradigm in which "composition of small things to make bigger things" isn't a fundamental design principle? A paradigm where making big things from small things is actually an anti-pattern?
- chrischen 6y agoWell if I were doing object oriented modeling I may have a `class Person` and some methods that operate on some Person data specific to my model. For example, I may model the person with name, age, job, and gender as a boolean... but in 2020 I may have to then reimplement the gender field entirely. If I later decide to have a `class Pet` it may share some common fields except for the "job" field. Having done this I may now do the opposite of composition in that I'm now factoring out the common properties of a Person and Pet and breaking them into smaller components rather than assembling them from smaller components. If I were doing this functionally and strictly typed I'd have started out with the leaf types of age, job, and gender that were agnostic to their owners (Person or Pet). They'd be implemented in pure way without dependencies or knowledge of a Person or Pet, can be tested and developed before any part of Person or Pet are implemented. Yes you could also compose classes, which are bundles of functions... but if you take that to the next level you'd get function composition. It's not that you can't do composable software with OOP... it's rather that functional programming is all about composition.