7 ms·
Functional core, imperative shell. This seems to be a good approach, especially when you push pretty hard to stuff as much as possible into the functional core
by michaelteter 3y ago
Functional core, imperative shell. This seems to be a good approach, especially when you push pretty hard to stuff as much as possible into the functional core.
Writing tests is much easier and requires much less code, and bugs are much easier to track down (since you have less surface area of "what might have modified this object I now hold?").
Since a functional approach also goes well with single-responsibility-principle and composability, you can end up with multiple times more actual functions. They're all (ideally) very conceptually simple, but the larger number of more specialized functions means you have to work a lot harder on choosing meaningful names. And to write meaningful names of specific functions, you can end up with some quite long names. This is actually quite ok as it makes things much more readable, but some people have some strong negative reactions to seeing 4-6 word function names. But at least in this case (Java being the most famous counter scenario) each function probably has more real value as it's not just OO layer artefacts.
- kmac_ 3y agoThis approach works very well. Side effects are pushed out of core and no mocks are required for end to end tests. It's quite hard to "sell" to fellow devs, but when it clicks, the code is very clean.
- bob1029 3y ago> Functional core, imperative shell. You can find this pattern in a lot of places if you are a little bit flexible with your definition of "functional". For example, why would we not consider some combination like SQL and PHP to be hinting at this exact same kind of archetype? You've got a crusty outer shell of yucky hackarounds (PHP) that talks to this (ideally) well-normalized & clean data store (SQL). Assuming you "stuff as much as possible" into core part of the solution, you could swap the outer shell for anything you desire without much headache.
- skrebbel 3y agoPHP is more like “imperative core, functional shell”. After all, a PHP request is a pure stateless function call. The only way it can modify state anywhere is with IO (eg store something in the database, write something to the session store, etc) and all data you accumulate in your PHP code gets purged after each request.
- closeparen 3y agoAnd SQL is all about mutation... functional take on the database would be something like Datomic.
- epgui 3y agoSQL is a weird beast. Very much on the contrary, the language has a relational core (note that functional is a special case of relational), at least in principle, but it’s been grafted with so many imperative-style hacks over time.
- michaelteter 3y agoI don't think it is "all about mutation". Indeed it has create/update/delete, but there are a ton of functional capabilities. So much of what SQL offers is related to data shaping. You can have a huge read-only data store and do amazing things with it.
- Culonavirus 3y agoThat's a hard disagree. The absolute VAST MAJORITY of SQL complexity is in the select queries (joins, groups, aggregation, window functions, cursors, filtering, procedures...), which are mostly not about mutation.
- closeparen 3y agoYou’re right about SQL the language, but I think the role of MySQL in a PHP application is much more to manage state than to provide interesting SELECT queries. The popularity of ORM in this world shows that what 95% of applications are looking for is native-ish collections that can be mapped to disk and updated concurrently. PHP’s shared nothing architecture means it must externalize this responsibility, and MySQL/Postgres are the customary choices. With actual SQL, most people don’t get beyond JOIN, which they mainly need in order to deserialize nested or pointer structures from normal form. I learned aggregation, grouping, and windowing working with Spark and Hive on warehouse replicas of my DBs/topics to troubleshoot and analyze my stuff. Never used one in an actual request handler.
- seanwilson 3y agoCan anyone explain why not "functional core, functional shell"? Is this partly because the shell has to mostly interact with things like UI APIs, file system APIs, network APIs and database APIs that are usually implemented in an imperative way already so you'd make your life harder this way? What if the APIs were written in a functional style? For example, I find in TypeScript, most libraries and the language itself are not written with immutability in mind so there's constant gotchas. So while I strongly prefer immutable data structures, going against the grain to use immutability everywhere usually isn't worth the frustration. That for me doesn't mean immutability is a bad idea though, it's more that the libraries and languages support is lacking.
- xupybd 3y agoThe shell is where you put all your side effects. Things like user input or writing to files. That way you can use pure functions on the inside. Pure functions don't allow for side effects.
- seanwilson 3y agoWhy does "imperative" imply "side effects" here though? Pure functional languages have ways of modelling side effects.
- xupybd 3y agoThey do but it's a bit of a square peg round hole. It's just easier not to use things like the IO monad.
- WorldMaker 3y agoIt's also fun to note that the IO monad in some perspectives is an abstraction for "functional core, imperative shell" moving as much of the imperative "order matters" computations to an outer "shell". The IO monad in some ways is the way of mathematically representing the "shell" of an application.
- gugagore 3y agoOne thing that I have seen in the Julia ecosystem, where multiple dispatch is pervasive, is that function names can get away with only being verbs. Without dispatch, you often see an approximation by baking in (Hungarian notation) the name of a type into the function. This is most clearly seen with conversation functions between types defined in different libraries. A collection class might have a method like "from_Array".
- dgb23 3y agoRelated to this pattern: I recently wondered why Casey Muratori (RAD Game tools, Handmade Hero etc.) seems to write C++ in a very C like fashion plus a few select features. Here's a discussion: https://hero.handmade.network/forums/code-discussion/t/453-why_use_c++_instead_of_c https://hero.handmade.network/forums/code-discussion/t/453-w... One of the takeaways is that he prefers the simplicity of C but uses operator overloading for similar reasons as you describe. One example includes addition of vectors, which is certainly a thing you do often in game and graphics related code.
- substation13 3y agoRich Hickey calls these protocols. In Haskell or Rust you get them via traits.
- yakubin 3y agoYour functional core also includes an imperative core inside it. Most of your “pure” functions modify the process page table and GC roots. Basically all the runtime is imperative. Finally some of your functions are written in something equivalent to the ST monad to have reasonable performance.