>
The other way around feels nonsensical to me.Wonder if that's because you've learned the pattern - "functional core, imperative shell"? I feel it's not a coincidence you used the terms "shell" and "core" in your comment :).
Myself, I'm very much in favor on a theoretical basis. In practice, I've got bitten by this a couple times - keeping a purely functional core didn't play nice with the modularization and testing setup we had, using C++17 with CMake and GTest/GMock. I blame this on my inexperience, filing off sharp corners as I go along. I'd sure could use a detailed study of how to apply this pattern in modern C++, on an application scale, taking account the real tradeoffs - like how to "firewall" the core from outside mess, and allow tests to poke through that firewall, and not end up in weird linking hell, and not blow a single module into 10 tiny ones, and keep the whole thing debuggable in practice[0].
One thing I occasionally do even in the "FP inner core" is apply the idea I picked up from Clojure's "transient" data structures: there is no difference between purely-functional, immutable, referentially transparent code, and imperative code carrying mutable state, if no one on the outside can tell. Sometimes it's just easier to make a typical imperative procedural or OOP solution and isolate it in a functional interface.
--
[0] - I used to like point-free coding; especially with functional style, it makes for nice, readable, highly-expressive one-liners. But each time I had to debug such code, I ended up regretting it. Now I'm starting to favor more explicit steps with intermediary variables storing results, simply to have good targets for potential breakpoints/tracepoints, and because such code is more convenient to step through. You could say I'm only doing it because the tools I use are limited, but frankly, almost all of "readable code" principles and even the shape of modern languages are driven by limitations of the tools we use, so ¯\_(ツ)_/¯.