[HN Gopher] Component Simplicity
___________________________________________________________________
Component Simplicity
Author : todsacerdoti
Score : 51 points
Date : 2025-03-18 09:00 UTC (3 days ago)
(HTM) web link (jerf.org)
(TXT) w3m dump (jerf.org)
| tantalor wrote:
| > Imperative code often devolves into writing things that
| exponentially expand the state space of your program and hoping
| you can find a happy path through your newly-expanded space
| without wrecking up too many of the other happy paths
|
| There's a good reason for this: it is the only way to run a
| business where the needs of customers and management changes on a
| constant basis.
|
| > Functional programming generally involves slicing away the
| state space until it is just what you need.
|
| This may be great idea for academics, but this approach is
| contraindicated in the field of software engineering, where the
| preferred solution is not the one with the "smallest state
| space", but the one that can deliver the most business impact
| with the least amount of effort. If I have to re-architect the
| entire program every Monday, that's not going to make leadership
| very happy.
| monooso wrote:
| > There's a good reason for this: _it is the only way_ to run a
| business where the needs of customers and management changes on
| a constant basis.
|
| (Emphasis mine)
|
| And yet there are businesses which run on Haskell.
| HPsquared wrote:
| The most widely-used functional programming environment in the
| world - Excel - is very adaptable and used in business.
|
| FP lets you add things freely without worrying about side
| effects.
| account-5 wrote:
| And likely the cause of a lot of business errors too. But at
| least it's not like parts of the human genome needed renamed
| because of it... Wait...
| naasking wrote:
| > If I have to re-architect the entire program every Monday,
| that's not going to make leadership very happy.
|
| Good thing FP doesn't require that then.
| eyelidlessness wrote:
| I think a more objective analysis would admit that it does
| require this, albeit _sometimes not every Monday_. And the
| article makes exactly this concession toward the end, to
| which I _suspect_ GP's comment was a glib reference.
|
| The objective counterpoint to this glib interpretation _IS
| NOT_ that, in FP, one never has to rearchitect the logic
| narrowing a program's state space. Instead, an objective
| counterpoint might be more along the lines of this:
|
| Yes, sometimes business rules evolve in a way that diverges
| from business logic. Yes, in a codebase that narrows its
| state space according to the prior business rules, that
| evolution may require revision to how state space is
| narrowed. But once that's done, _it is done_. It doesn't
| require adjusting dozens of special cases incidental to the
| previous business rules' implementation: it only requires
| adjusting the special cases inherent to the business rules.
|
| That's objective. And it's also objective to say that whether
| it's _compelling_ will depend on the business's _tolerance
| for that objectivity_ (or in lucky cases: its embrace of
| same).
| prerok wrote:
| I mean, isn't the problem you are mentioning because of the
| functionality that is assuming too much? In the sense of
| functions that are behemots of assumptions?
|
| It's actually easier if those functions are broken down into
| smaller pieces so when requirements change, you don't have to
| rearchitect: you just recombine them differently.
|
| And I am not speaking academically but practically: have
| learned the hard way to not produce the behemots but to combine
| functions to the desired effect in business setting(s).
| zackmorris wrote:
| An insight that might help tie functional programming (FP) and
| imperative programming (IP) together is that FP is spreadsheets
| and IP is macros (edit: recordings of human interaction and shell
| scripts). Ideally all business logic should be FP and event-
| driven interfaces are usually IP. I think of these as clusters of
| similar concepts: FP synchronous blocking
| immutable process isolation auto optimization
| auto parallelization higher-order methods scatter-
| gather, fork-join deterministic/repeatable (sometimes)
| reversable execution models and views in MVC ugly
| prefix/postfix syntax that doesn't look like algebra (not always)
| IP asynchronous nonblocking mutable shared
| state manual optimization manual parallelization
| generators/iterators threads, locks/mutexes
| nondeterministic promises/futures requiring snapshots to reverse
| execution controllers in MVC pretty infix syntax that
| looks like algebra (not always)
|
| I believe that ideally we'd write everything in FP with no IP
| whatsoever. So no mutable variables, no pass by reference, no
| iteration. Just higher-order methods and copy-on-write, with
| static analysis simplifying intermediate code into its fastest
| and/or most concise representation to run in parallel as fast as
| possible on multicore CPUs. Each component runs single-shot using
| analogs of STDIN/STDOUT/STDERR. These can fully represent models
| and views functionally and even declaratively.
|
| Then IP would be used to wire up components, similarly to
| controllers in MVP. For example when a UNIX executable blocks
| waiting for more data on an input stream, or more room in an
| output stream. The runtime/filesystem/network/OS between isolated
| executables is imperative.
|
| FP and IP can be unified by enforcing immutability everywhere in
| an IP language, then transpiling the code to reshape it between
| prefix/infix/postfix notation. Unfortunately I/O still can't be
| implemented without monads, making this psuedo-FP language
| impure. So there may be no way to build an entirely pure FP
| runtime that can respond to dynamic input/output and exceptional
| behavior. It would be like having a spreadsheet with no
| connection to the outside world, where the values of cells can't
| be edited, so formulas could run but they never have reason to do
| so.
|
| Attempts can also be made to eliminate IP altogether by providing
| impure functionality in FP languages by introducing monads for
| I/O and exceptional behavior, allowing mutation by isolating it
| in blocks, etc. I believe that makes impure FP languages
| equivalent to IP, corrupting them and defeating the point of
| using FP. Sometimes that makes sense though if/when we can live
| with the tradeoffs, or if we're porting code from IP languages
| that's too difficult to refactor to pure FP. Unfortunately most
| impure FP languages are also difficult to read by humans,
| limiting their adoption.
|
| Without the basic understanding listed here, the world seems to
| naturally head towards IP, since it most closely mimicks how
| humans navigate the world. IP languages tend to borrow FP
| concepts like monads to implement async behavior like generators,
| futures and promises. This sacrifices determinism and expands
| shared state to such a degree that IP programs can only grow to a
| certain size before they are intractable. This can sort of be
| avoided with reducers like Redux or writelog databases, usually
| at a cost in having to maintain boilerplate.
|
| The world also tends to double down on static types, categories,
| templates/generics, formal contracts/interfaces, etc, borrowed
| from FP without a clear understanding of when or why they're
| necessary. When often programs can be written much more concisely
| and clearly by avoiding custom types altogether and focusing on
| data-driven standard types like numbers, strings and JSON run
| through transformation functions. An obvious example being how
| Java programs are often bloated and overengineered because they
| are object-oriented instead of functional. It's amazing to see
| how so much boilerplate often contains so little actual business
| logic. And how languages inspired by Java, like C#, often seem to
| repeat its shortcomings.
|
| I think of all of this as analogous to how beginners tend to
| implement a game's main loop with state machines for each sprite.
| Whereas in Unity they can use coroutines. Once we see how
| straightforward it is to write business logic in a one-shot
| fashion, it's hard to imagine going back to juggling the
| complexities of state machines. Especially since state machines
| and coroutines can be made equivalent via generators.
|
| Another example is that digital electronic circuits are
| equivalent to a spreadsheet and can be represented and analyzed
| by FP, but it's hard to convert an IP program description into a
| circuit or analyze its behavior formally using techniques from
| logic.
|
| Once we have these kinds of insights, it's difficult to unsee
| them. And to look at best practices without asking ourselves why
| we're doing things the hard way.
|
| As far as I know, the only language that approximates pure FP
| with IP glue handled by the runtime to avoid monads is
| ClojureScript. Admittedly, I'm probably misunderstanding how it
| works.
| ryandv wrote:
| > I believe that ideally we'd write everything in FP with no IP
| whatsoever. So no mutable variables, no pass by reference, no
| iteration.
|
| > Then IP would be used to wire up components, similarly to
| controllers in MVP.
|
| This viewpoint was popularized by Gary Bernhardt a while ago as
| "functional core, imperative shell." [0] Functional paradigms
| should be used for expressing core logic and transformation of
| data; code written in this style is side-effect free, "pure,"
| rapidly unit testable in isolation, and even could in principle
| be executed in one's head, because it's the final _expression
| value_ that is the goal when working in this paradigm.
|
| Imperative paradigms on the other hand are not interested as
| much with expressions and values, but rather _statement side-
| effects_ , behaviors, and the _sequencing_ together of
| computations (and it 's the concept of sequencing together
| effectful computations that monads attempt to abstract over).
| It is the glue code that ties together your pure, effect-free
| logic and embeds those expressions in a context where they
| actually have some interaction with computing, hardware, and
| the outside world; otherwise, they would just be formulae
| sitting in a spreadsheet with no connection to anything
| whatsoever.
|
| > Attempts can also be made to eliminate IP altogether by
| providing impure functionality in FP languages by introducing
| monads for I/O and exceptional behavior, allowing mutation by
| isolating it in blocks, etc. I believe that makes impure FP
| languages equivalent to IP, corrupting them and defeating the
| point of using FP.
|
| This point is interesting. One could argue that monads are in
| fact a way of _preserving_ purity in FP while still attaining
| effectful imperative-like behavior, since the monad is "just"
| an AST or a pure value that _describes_ a sequence of
| computations that _ought_ to take place, and it 's up to the
| runtime to _actually_ interpret this data structure and execute
| or realize the effects merely described by the monadic value (I
| suppose an OOP design patternist would see similarities to a
| "Command pattern" here); however, Conal Elliot has taken this
| logic to the conclusion that, if it were the case, then the C
| language must also be "functionally pure." [1]
|
| > When often programs can be written much more concisely and
| clearly by avoiding custom types altogether and focusing on
| data-driven standard types like numbers, strings and JSON run
| through transformation functions. An obvious example being how
| Java programs are often bloated and overengineered because they
| are object-oriented instead of functional. It's amazing to see
| how so much boilerplate often contains so little actual
| business logic. And how languages inspired by Java, like C#,
| often seem to repeat its shortcomings.
|
| Maybe, but there is also a risk of running into the "Primitive
| Obsession" code smell [2]. If I have a URL, representing that
| URL as a String now allows malformed URLs or things that are
| not URLs at all to inhabit that type, reducing the number of
| static guarantees I can avail myself of; moreover, if I want to
| extract the scheme, path, etc. from the URL I now have to
| extract those attributes by way of lower-level text
| manipulation, substrings, and slices, instead of being able to
| simply read off those components of the URL from a higher-level
| representation that actually reifies those components as first-
| class attributes of a more structured data type.
|
| I find that one of the main differences between Haskell and
| other languages and their ecosystems is the focus on finding
| _good abstractions._ Often I have seen Java or C# or golang
| interfaces two dozen methods wide. Not only does this break
| "interface segregation principle" and other SOLID dogmas,
| making it difficult to write other implementors (and often you
| just end up with the single implementor, at which point the
| interface barely abstracts over anything at all), such an
| overspecified interface tends to generalize poorly to other use
| cases.
|
| The more you add to your interface, the more you specify and
| constrain its structure, the less general it becomes; hence
| TFA's celebration of monads which, having a minimal complete
| definition of _one operator,_ can be applied to a shockingly
| wide variety of domains and are widely relied on by the
| ecosystem because, as an abstraction only a method wide, monads
| are subject to much less churn or interface breakage over time.
|
| Finding good abstractions is hard, and IMO one of the central
| problems of writing software. I would summarize the article as
| observing that in imperative programming, it's possible to
| proceed naively but expeditiously, papering over the lack of
| strong and consistent abstractions by just throwing more glue
| code and flex tape at the problem to paper over any leaks. In
| Haskell, especially when first designing your types and data
| structures, the work of abstracting the problem domain
| adequately is front-loaded and you are forced to think about it
| up front.
|
| [0]
| https://www.destroyallsoftware.com/screencasts/catalog/funct...
|
| [1] http://conal.net/blog/posts/the-c-language-is-purely-
| functio...
|
| [2] https://wiki.c2.com/?PrimitiveObsession
___________________________________________________________________
(page generated 2025-03-21 23:02 UTC)