[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)