5 ms·
> 100% purity down the entire vertical is a very strong anti-pattern. It really isn't. The benefit of pure all the way down is that later you can replace bits
by louthy 1y ago
> 100% purity down the entire vertical is a very strong anti-pattern.
It really isn't. The benefit of pure all the way down is that later you can replace bits with something more performant if necessary. But starting out with the idea that some bits should never be pure just means none of it is. The beauty of pure functional programming is that its very compositional nature means that you can replace a component without having a detrimental effect to everything its composed with: as long as you maintain referential transparency for that component.
What we gain from imposing the pure functional constraints on ourselves is genuine composition. If we compose two pure functions into a new function, that resulting function will also be pure. This is the pure functional programming super power that leads to fewer bugs, easier refactoring, easier optimisation, faster feature addition, improved code clarity, parallelisation for free, and reduced cognitive load. Opting out for arbitrary reasons at certain stages of the vertical threatens all of that.
- mrkeen 1y agoAll of this! "Pure functional" isn't a style choice. The judges won't hold up big placards with 10 on them because you executed your business logic in style. It's a contract that your code will give the same output given the same input. The contract goes both ways: your ability to supply a caller with functional code is made easier by your callees supplying you with functional code. Someone decides that that's draconian and says "what's the worse that can happen?" and starts mutating in a library dependency. Well now your backtracking parser may or may not be able to backtrack. Your transactional code may or may not be able to be rolled back. Your multi-threaded code may or may not be free of races. `true || f()` no longer means the same thing as `true`. You might need to start scaffolding all your unit tests with @Before and @After to setup and teardown state, and give up running them in parallel. Maybe you really, really, really need to fire the missiles at the bottom of the call chain. Fine, just mark that method as 'red' so I get a compiler error when I try to serve up 'blue' code to my callers. As long as you're showing off the syntax (matter of taste) on blog posts you might as well get some mileage out of the semantics (guarantees).
- pjc50 1y agoThe problem is this tends to collide hard with things which are both stateful and mandatory ubiquitous, like logging. "Functional core, imperative shell" is a great tradeoff position though. >> Does F# care if a DLL it references was coded in a functional style Deeper problem: it can't know. It can only assume. I'd have to check how the loader works but it may be the case that "first call to a function in an external DLL" is not stateless (and can error!) because it triggers the linker.
- louthy 1y agoState can also be managed using the pure functional paradigm (using recursion to provide an updated state as an argument or using a State monad, for example). Logging is a side-effect and should be treated as such by using a declarative side-effect type, like an IO monad, or could be tracked for later persistence with a Writer monad. The oft repeated “Functional core, imperative shell” is just nonsense when you try to do it for real. It’s one of those dogmatic statements that gets dragged out in these types of discussions and adds nothing. This is why effect monads are so useful: there’s no need to create arbitrary boundaries to have effectful and pure code. Alan Turing already proved the equivalence between lambda calculus and the Turing Machine: anything imperative can be done functionally. There really is no need to create arbitrary boundaries. In languages like C# it may be pragmatic to do so in certain circumstances, like making ASP.NET request-handlers invoke pure functional computations and return the result of the invocation. This is pragmatic/preferable to writing a competitor to ASP.NET. Outside of situations like that I don’t see any value to an ‘imperative shell’, whatever that actually means.
- mrkeen 1y agoIf logging is non-functional, then you can't log in a functional core. Luckily you can log functionally.