<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matti | funkyposts</title>
    <description>The latest articles on DEV Community by Matti | funkyposts (@mahush).</description>
    <link>https://dev.to/mahush</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3819062%2F9b4cb86c-0b56-4bc1-9a17-df4bbd789d09.png</url>
      <title>DEV Community: Matti | funkyposts</title>
      <link>https://dev.to/mahush</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mahush"/>
    <language>en</language>
    <item>
      <title>Mastering State in Modern C++: Making It Explicit</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Wed, 06 May 2026 07:30:00 +0000</pubDate>
      <link>https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o</link>
      <guid>https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o</guid>
      <description>&lt;p&gt;&lt;strong&gt;Passing state as data in the functional core–imperative shell&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In an &lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;earlier post&lt;/a&gt;, we saw that the &lt;em&gt;functional core – imperative shell&lt;/em&gt; pattern reduces complexity by centralizing state mutation in the shell, turning hidden state dependencies into explicit ones.&lt;/p&gt;

&lt;p&gt;The underlying idea is simple: keep state out of the business logic. But putting this into practice is not: how can the business logic still evolve state that lives elsewhere—and why does this make dependencies explicit?&lt;/p&gt;

&lt;p&gt;Without a clear model at the code level, state easily becomes an implicit dependency again—undermining the whole design.&lt;/p&gt;

&lt;h2&gt;
  
  
  How State Flows Through Core and Shell
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;functional core – imperative shell&lt;/em&gt; pattern separates business logic from side effects. State handling is just another side effect. For state, this means that although the business logic in the core drives changes, it does not persist or mutate state.&lt;/p&gt;

&lt;p&gt;This is resolved by letting state live in the shell, while the core receives it as input and returns updates. So, state evolution follows a clear pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;persisted in the shell&lt;/strong&gt;: the shell holds the current state and passes it as data into a core function
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;driven by the core&lt;/strong&gt;: the core function computes and returns an updated state
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mutated in the shell&lt;/strong&gt;: the shell applies the returned state
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, the business logic still determines how state evolves—but without persisting or mutating it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub7qymin326a7fvpgqe7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub7qymin326a7fvpgqe7.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s dive into an example, to see how state flows between shell and core&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing It in Code
&lt;/h2&gt;

&lt;p&gt;Here is a concrete example from my &lt;a href="https://github.com/mahush/funkysnakes/tree/v0.1.0" rel="noopener noreferrer"&gt;funkysnakes github project&lt;/a&gt; that illustrates the pattern clearly. The project implements the actor-based &lt;em&gt;functional core–imperative shell&lt;/em&gt; architecture introduced in an &lt;a href="https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6"&gt;earlier post&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In this example, the snakes are part of the game state, and moving them means evolving that state.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GameEngineActor&lt;/code&gt;, as shell, holds the &lt;code&gt;GameState&lt;/code&gt; struct that aggregates the relevant sub-states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Shell: where state is persisted&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameEngineActor&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameEngine&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;GameState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PerPlayerSnakes&lt;/span&gt; &lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;FoodItems&lt;/span&gt; &lt;span class="n"&gt;food_items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Board&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="n"&gt;GameState&lt;/span&gt; &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core provides the pure function &lt;code&gt;moveSnakes&lt;/code&gt; depending on the sub-states &lt;code&gt;snakes&lt;/code&gt;, &lt;code&gt;board&lt;/code&gt;, and &lt;code&gt;food_items&lt;/code&gt;. It advances the snakes by one step, returning updated &lt;code&gt;snakes&lt;/code&gt; while &lt;code&gt;board&lt;/code&gt; and &lt;code&gt;food_items&lt;/code&gt; are only read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Core: where state evolution is driven&lt;/span&gt;
&lt;span class="n"&gt;PerPlayerSnakes&lt;/span&gt; &lt;span class="nf"&gt;moveSnakes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PerPlayerSnakes&lt;/span&gt; &lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Board&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FoodItems&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;food_items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, &lt;code&gt;moveSnakes&lt;/code&gt; is called by the shell within the game loop of the &lt;code&gt;GameEngineActor&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Shell: where state is mutated&lt;/span&gt;
&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snakes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;moveSnakes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;food_items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example maps to the &lt;em&gt;functional core–imperative shell&lt;/em&gt; design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the core is the pure function &lt;code&gt;moveSnakes&lt;/code&gt; and the data structures it operates on
&lt;/li&gt;
&lt;li&gt;the shell is the &lt;code&gt;GameEngineActor&lt;/code&gt;, which holds and mutates the state
&lt;/li&gt;
&lt;li&gt;both connect at the function call, where the core’s result is applied to the state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A key detail is how state is perceived differently. In the shell, &lt;code&gt;GameState&lt;/code&gt; persists across calls and is mutated over time—this is what makes it state. In the core, however, there is no notion of state—only data passed in and returned. This is exactly what allows pure functions to drive state changes without mutating state themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Design Gives You
&lt;/h2&gt;

&lt;p&gt;Looking at the following benefits through the lens of dependencies reveals why they arise:&lt;/p&gt;

&lt;p&gt;One key aspect is &lt;em&gt;transparency&lt;/em&gt;. All state appears at the top level, making it obvious which state exists. State changes are explicit and easy to follow, rather than scattered deep inside objects as is often the case in nested OOP designs. What makes this possible is that state dependencies are no longer hidden, but explicit.&lt;/p&gt;

&lt;p&gt;There's also high &lt;em&gt;flexibility&lt;/em&gt; in which state can be processed by which function. Here the &lt;code&gt;snakes&lt;/code&gt; sub-state is mutated, but depends on the &lt;code&gt;board&lt;/code&gt; and the currently existing &lt;code&gt;food_items&lt;/code&gt;. Whatever data a pure function needs can simply be passed in by the shell. This flexibility comes from decoupling logic from stored state and connecting both only through data passed to the function, keeping dependencies simple and explicit.&lt;/p&gt;

&lt;p&gt;And another great benefit of this design is that it improves &lt;em&gt;testability&lt;/em&gt; significantly. As pointed out in the &lt;a href="https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47"&gt;intro post&lt;/a&gt;, testing stateful code is often complex. To test a specific behavior, you first need to get your software into the right state. This means using the regular API, test-specific APIs, or mocks. This changes when the business logic no longer depends on hidden internal state, but only on explicit input. With these dependencies exposed, you can simply pass in whatever state you need, making testing straightforward.&lt;/p&gt;

&lt;p&gt;In conclusion, all of these benefits stem from the same shift: state dependencies become explicit instead of hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Reaches Its Limits
&lt;/h2&gt;

&lt;p&gt;So far, we looked at state that has meaning at the domain level: snakes, food items, and the board. This kind of state belongs to the game model, so it makes sense that the game engine shell holds it explicitly and passes it into the core.&lt;/p&gt;

&lt;p&gt;But not all state should be understood at that level. Some state only exists to support a specific module—parser state, cache state, or other implementation details. Exposing all of that at the domain level would clutter the shell with details it should not need to understand—blurring the domain model and making the system harder to reason about.&lt;/p&gt;

&lt;p&gt;So the next question is how to reintroduce encapsulation without giving up the functional mechanics we established here.&lt;/p&gt;

&lt;p&gt;In the upcoming post, we will look at how to handle such &lt;em&gt;module-internal state&lt;/em&gt; while keeping state evolution explicit and dependencies under control.&lt;/p&gt;




&lt;p&gt;This post is created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v01)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bridging Object-Oriented and Functional Thinking in Modern C++</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Wed, 29 Apr 2026 07:15:45 +0000</pubDate>
      <link>https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47</link>
      <guid>https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47</guid>
      <description>&lt;p&gt;&lt;em&gt;How dependencies make C++ systems hard to test and evolve—and why functional thinking changes it&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fighting complexity is crucial if you want to succeed in software development. In many real-world C++ projects, however, systems tend to evolve in the opposite direction. Components become tightly coupled, so changes ripple through large parts of the system. State is scattered, reducing transparency about how it evolves over time. Behavior emerges from many interacting parts, making it hard to reason about individual pieces in isolation. &lt;/p&gt;

&lt;p&gt;This also shows up in testing: a significant amount of boilerplate and indirection is needed just to enable it. Establishing specific system states requires substantial setup, and maintaining tests becomes costly, as even small production changes force updates across large parts of the test code.&lt;/p&gt;

&lt;p&gt;The result is a system that is hard to get right—and once it works, it feels rigid and difficult to evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where OOP Leads to Complexity
&lt;/h2&gt;

&lt;p&gt;The underlying problems unfold nicely when jumping into the details from the testing point of view. So, let's focus on unit-testable code, that is code structured in smaller parts that can be executed in isolation. To get there we apply the “separation of concerns” principle, which provides the units for testing. And to actually run modules independently, dependency injection comes into play. This means if one module depends on another, it is passed in, normally as a constructor argument. In a unit test where a single module should run in isolation, all dependent modules need to be mocked. So, instead of real dependencies, the test passes its mocks as constructor arguments.&lt;/p&gt;

&lt;p&gt;This reveals the first issue: &lt;strong&gt;No testing without mocking&lt;/strong&gt;. Technically that mocking approach works well—unit testing is enabled. But actually it comes at a high price. The mocks need to be written and maintained, which increases test-specific code and in turn leads to higher test complexity. And we should not neglect this, as only if all the mocks are implemented correctly, the test is helpful.&lt;/p&gt;

&lt;p&gt;Mocks must implement the same interface as the real components they replace, which leads to another problem: &lt;strong&gt;Interfaces create tight coupling between alternative implementations&lt;/strong&gt;. Often these interfaces contain several interdependent function signatures with non-trivial pre- and post-conditions. Any change affects all implementations and thus also all mocks, which increases maintenance effort. With dependency injection and many mocks, this interface-level coupling becomes especially visible and expensive. So, we often trade testability for flexibility here, which can be a poor deal.&lt;/p&gt;

&lt;p&gt;But the challenges don’t stop at interfaces. Once state comes into play, things get even more complicated: &lt;strong&gt;State encapsulated within a class is hard to modify by a test&lt;/strong&gt;. In traditional OOP, private mutable state is fully hidden, so tests cannot set it up directly. A test that needs the object in a specific internal configuration must drive the state there indirectly through the public interface, which often requires multiple method calls, complex sequences, and mocks. This indirect setup adds complexity and makes tests more brittle.&lt;/p&gt;

&lt;p&gt;Actually, state handling in a traditional OOP manner has another problematic dimension: &lt;strong&gt;Generally state is cluttered across the object graph&lt;/strong&gt;. This introduces state dependencies that are hard to see. The more stateful objects exist, the harder it gets to reason about how state evolves in the system. Of course, this compounds the state-related test complexity mentioned earlier.&lt;/p&gt;

&lt;p&gt;Interestingly, testing just makes these problems visible—but they exist in the design itself. E.g. the tight coupling of implementations sharing an interface can be described more generally as: &lt;strong&gt;Inheritance creates tight coupling within the hierarchy&lt;/strong&gt;. So, once a class hierarchy is established, the base class interface becomes almost impossible to change without impacting every derived class. This gets worse the deeper the hierarchy is. Even small adjustments propagate through, making class hierarchies inflexible and expensive to modify.&lt;/p&gt;

&lt;p&gt;Let's step back and draw conclusions. All these problems create a lot of complexity. This way they cause the pain described in the beginning. But there is more to understand here: &lt;strong&gt;The root cause is dependency&lt;/strong&gt;—complexity emerges as different kinds of dependencies accumulate: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;components dependent on each other based on injection can only be executed in isolation by introducing mocks&lt;/li&gt;
&lt;li&gt;the broader an interface, the more complex the dependency between components sharing it&lt;/li&gt;
&lt;li&gt;inheritance causes dependencies between classes within hierarchies&lt;/li&gt;
&lt;li&gt;hidden, distributed, and evolving state creates implicit dependencies across the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every issue we looked at comes down to this: dependencies force parts of the system to change together, to be set up together, and to be understood together. As they reinforce each other, complexity grows increasingly fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Functional Thinking Unlocks
&lt;/h2&gt;

&lt;p&gt;So, I started questioning the design ideas I was following for years and searched for different ways of structuring my code to better manage these dependencies. The blog post &lt;a href="https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a" rel="noopener noreferrer"&gt;Mocking is Code Smell&lt;/a&gt; helped me see this more clearly. It also pushed me to explore functional ideas that turned out to be particularly useful. Put simply, functional programming offers techniques that can complement a traditional C++ toolbox.  &lt;/p&gt;

&lt;p&gt;These techniques work because they change how we design systems—and with that, which dependencies emerge. To get an idea of what changes, consider pure functions as building blocks for your logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pure functions receive all dependencies explicitly as input, instead of relying on injected objects. This eliminates implicit dependencies and significantly reduces the need for mocking.&lt;/li&gt;
&lt;li&gt;In a world of pure functions, there is no hidden state, so dependencies on state are always explicit and visible.&lt;/li&gt;
&lt;li&gt;Function interfaces are typically narrower than class interfaces, which keeps dependencies simpler.&lt;/li&gt;
&lt;li&gt;There is no inheritance hierarchy, removing an entire class of dependencies &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conclusion, when you compose your business logic out of pure functions, dependencies become explicit, fewer, and simpler. This directly mitigates complexity.&lt;br&gt;
The design becomes clearer, and as a result, reasoning, testing, and maintenance improve.&lt;/p&gt;

&lt;p&gt;Just to be explicit, I am not saying functional programming is going to solve all problems, nor that it’s applicable in C++ without limits. I am saying there are aspects that are highly valuable when designing C++ systems. So, I advocate for complementing OOP with FP where appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  If This Resonates
&lt;/h2&gt;

&lt;p&gt;Adopting a different way of structuring code takes effort, and how much depends on your learning path. If you’re coming from an OOP-heavy, C++-style background like I did, I might speak your language well enough to further clarify these techniques and design choices. &lt;/p&gt;

&lt;p&gt;My original motivation wasn’t to write about system design or functional programming. I simply wanted to figure out how to apply these ideas effectively in real-world C++ code. After exploring this for a while and finding techniques that work, it feels natural to share and discuss them. &lt;/p&gt;

&lt;p&gt;The funkyposts blog is meant to build a bridge between traditional C++ and functional programming by illustrating practical FP patterns in modern C++. The goal is to provide concrete ways to improve how systems are structured—while staying grounded in real-world constraints. I also welcome feedback and different perspectives to further refine these ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dive deeper
&lt;/h2&gt;

&lt;p&gt;The next step is to leverage these insights in practice—improving existing designs without rewriting everything. The subsequent posts in this series show how.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;Handling Side Effects in Modern C++: Interfacing Pure Functions with Our Imperative World&lt;/a&gt;&lt;/strong&gt; — Using the functional core–imperative shell pattern to mitigate dependencies&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6"&gt;When One Shell Isn’t Enough: Scaling the Functional Core–Imperative Shell Pattern with Actors in C++&lt;/a&gt; — How to structure growing C++ systems into actor-driven core–shell pairs that isolate dependencies&lt;/li&gt;
&lt;li&gt;More posts in this series coming soon&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Part of the &lt;em&gt;funkyposts&lt;/em&gt; blog — bridging object-oriented and functional thinking in C++.&lt;br&gt;
Created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v06)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>When One Shell Isn’t Enough: Scaling the Functional Core–Imperative Shell Pattern with Actors in C++</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Fri, 10 Apr 2026 08:15:26 +0000</pubDate>
      <link>https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6</link>
      <guid>https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6</guid>
      <description>&lt;p&gt;&lt;strong&gt;How to structure growing C++ systems into actor-driven core–shell pairs that isolate dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The previous &lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;post&lt;/a&gt; showed how the &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern decouples the business logic from side effect handling by centralizing all side effects in the shell.&lt;/p&gt;

&lt;p&gt;But as the application grows, handling different kinds of side effects (IO, state, timers) for the entire system in a single place quickly becomes problematic: more and more unrelated concerns end up sharing the same context. As a consequence, they can accidentally influence each other, or at least make it harder to see which dependencies actually exist. The shell has to be sorted out again and again, making the code harder to reason about and maintain.&lt;/p&gt;

&lt;p&gt;This raises the crucial question: how do we scale the &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern without tying together parts that shouldn't influence each other?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Design Idea: Actors as Shell
&lt;/h2&gt;

&lt;p&gt;In order to keep things clean, we need to decouple unrelated shell concerns. Thus, a single shell isn’t enough—we need multiple ones, each handling an independent, non-overlapping subset of side effects.&lt;/p&gt;

&lt;p&gt;To fully understand what this means, let’s look at this design from the business logic point of view: effectively, the business logic is split into multiple cores, each with its own shell that handles only the side effects it requires. Thus, the system is structured into multiple core–shell pairs, each forming its own isolated unit around a specific concern. Each unit owns its behavior end-to-end—from handling input effects in its shell, through business logic in the core, to producing output effects via the shell. This is not just splitting the shell, but partitioning the system into smaller, self-contained slices that evolve independently.&lt;/p&gt;

&lt;p&gt;One concrete option to put this idea into practice is using the actor model. Actors are inherently separated from each other, so they naturally provide the needed isolation. The approach is simple: combine the actor model with the &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern and implement shells as actors. This allows multiple shells to coexist cleanly while still being able to communicate through messages. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1h54fta1ab8cyi4l2l4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1h54fta1ab8cyi4l2l4n.png" alt=" " width="800" height="818"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real-World Example
&lt;/h2&gt;

&lt;p&gt;Let’s see this design in action with an example from my &lt;a href="https://github.com/mahush/funkysnakes/tree/v0.1.0" rel="noopener noreferrer"&gt;funkysnakes&lt;/a&gt; project. I prototyped &lt;a href="https://github.com/mahush/funkyactors/tree/v0.1.0" rel="noopener noreferrer"&gt;the actor implementation there&lt;/a&gt; on top of the &lt;a href="https://github.com/chriskohlhoff/asio" rel="noopener noreferrer"&gt;Asio framework&lt;/a&gt;. It's quite lean, although it has similar semantics to ROS2 in terms of topic-based message passing. &lt;/p&gt;

&lt;p&gt;Building on this, the &lt;em&gt;funkysnakes&lt;/em&gt; game implementation is distributed across multiple actors, each following the core–shell pattern as described above.&lt;/p&gt;

&lt;p&gt;Let's focus on two actors that are connected by a topic that communicates the &lt;code&gt;DirectionMsg&lt;/code&gt;, representing a requested direction for the player's snakes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Direction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RIGHT&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;DirectionMsg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PlayerId&lt;/span&gt; &lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This message is published by the &lt;code&gt;InputActor&lt;/code&gt; that is responsible for handling user input once a player presses a direction key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputActor&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InputActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;public:&lt;/span&gt;
        &lt;span class="n"&gt;InputActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActorContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                   &lt;span class="n"&gt;TopicPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="n"&gt;direction_pub_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;create_pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processInputs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stdin_reader_&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tryTakeChar&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tryParseKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_parser_state_&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;key_parser_state_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;direction_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tryConvertKeyToDirectionMsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;direction_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="n"&gt;direction_pub_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;direction_msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;PublisherPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_pub_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;GameEngineActor&lt;/code&gt; receives the &lt;code&gt;DirectionMsg&lt;/code&gt; message and applies it to its game state &lt;code&gt;game_state_&lt;/code&gt;. Once the &lt;code&gt;game_loop_timer_&lt;/code&gt; triggers, the snakes are moved forward based on the latest direction. The processing of actor inputs like messages or timer events is coordinated in the &lt;code&gt;processInputs&lt;/code&gt; method, which is invoked whenever an input arrives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameEngineActor&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameEngineActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;public:&lt;/span&gt;
        &lt;span class="n"&gt;GameEngineActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActorContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                        &lt;span class="n"&gt;TopicPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;TimerFactoryPtr&lt;/span&gt; &lt;span class="n"&gt;timer_factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;direction_sub_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
          &lt;span class="n"&gt;game_loop_timer_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_timer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameTimer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timer_factory&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processInputs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;direction_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;direction_sub_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryTakeMessage&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// applies direction to game_state_ by calling the related pure function of the core here&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;elapsed_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;game_loop_timer_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryTakeElapsedEvent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// moves snakes forward by calling the related pure function of the core here&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;SubscriptionPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_sub_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;GameLoopTimerPtr&lt;/span&gt; &lt;span class="n"&gt;game_loop_timer_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;GameState&lt;/span&gt; &lt;span class="n"&gt;game_state_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In summary, the &lt;code&gt;InputActor&lt;/code&gt; asynchronously publishes player direction updates, while the &lt;code&gt;GameEngineActor&lt;/code&gt; consumes them and evolves the game state each time the game loop timer fires.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD

subgraph InputActor["Input Actor (Shell)"]
    InputCore["Functional Core"]
end

subgraph GameEngineActor["Game Engine Actor (Shell)"]
    GameCore["Functional Core"]
end

InputActor --&amp;gt;|DirectionMsg| GameEngineActor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this scales
&lt;/h2&gt;

&lt;p&gt;Of course, this modular design comes with some benefits, let's take a closer look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separation of Concerns: Clear Responsibilities and Boundaries&lt;/strong&gt;&lt;br&gt;
The input handling and game logic are completely decoupled. Each actor focuses on a narrow, well-defined concern. The &lt;code&gt;DirectionMsg&lt;/code&gt; forms their connection point which is independent of any specific actor, so the modules don’t depend on each other directly—they only agree on a shared data contract. This keeps the boundaries clean and prevents any form of tight coupling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplified Reasoning: Isolation&lt;/strong&gt;&lt;br&gt;
Complexity is distributed across multiple isolated shells. Each shell only handles its own side effects, so unrelated parts no longer share the same context and cannot accidentally interfere with each other. The &lt;code&gt;GameEngineActor&lt;/code&gt; cannot publish direction messages, and the &lt;code&gt;InputActor&lt;/code&gt; cannot access or modify the game state or the game loop timer. Each part is isolated and can be understood without needing to consider the behavior of the others.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fewer Bugs: Concurrency and Isolation&lt;/strong&gt;&lt;br&gt;
Processing key events and running the game loop happen asynchronously. Key events occur sporadically, while the game loop runs periodically via a timer. Each actor has its own single-threaded execution environment and processes events such as incoming messages or timer expirations sequentially. This eliminates low-level multithreading issues such as data races, because actors do not share mutable state and each actor mutates its state sequentially. Anyone who has spent days debugging low-level multithreading issues knows how valuable such a design is.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better Maintainability: Independent Scaling&lt;/strong&gt;&lt;br&gt;
You can add another input source, such as a Bluetooth controller, without touching the &lt;code&gt;GameEngineActor&lt;/code&gt;. You can even add it without touching the existing &lt;code&gt;InputActor&lt;/code&gt;, just adding another actor that publishes a &lt;code&gt;DirectionMsg&lt;/code&gt; message. Each module evolves independently.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applying the dependency lens from the &lt;a href="https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47"&gt;intro post&lt;/a&gt; gives another perspective on why this design scales: it’s not just the separation itself, but how it constrains dependencies. By isolating actors from each other, all cross-actor dependencies are ruled out upfront except those explicitly expressed through message passing. Without shared state or a shared execution context, message passing is the only way for parts of the system to influence each other. As a result, the system becomes easier to reason about, test, and evolve because fewer parts are able to influence each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Advantage: Bridging by Design
&lt;/h2&gt;

&lt;p&gt;There is another very important benefit that goes beyond modularity: The actor-based &lt;em&gt;functional core–imperative shell&lt;/em&gt; design is able to bridge naturally object-oriented and functional code via inter-actor communication.&lt;/p&gt;

&lt;p&gt;This is powerful in practice because the actor boundary lets both styles coexist without forcing the whole system into one paradigm. It's not a compromise—it's straightforward design: you choose which actor should follow which design based on your needs and the architecture just supports that choice. &lt;/p&gt;

&lt;h2&gt;
  
  
  Where we arrived at
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;functional core – imperative shell&lt;/em&gt; pattern scales by partitioning the system into multiple core–shell pairs. This isolates unrelated concerns like input handling and game state evolution, effectively preventing unintended dependencies between them.&lt;/p&gt;

&lt;p&gt;Sure, introducing this structure is not for free. The extra abstraction and message passing bring additional complexity that needs to be balanced against the advantages.&lt;/p&gt;

&lt;p&gt;However, when that additional structure is justified, you gain valuable isolation that simplifies reasoning and maintenance.&lt;/p&gt;

&lt;p&gt;On top of that, it enables clean bridging between traditional object-oriented design and a functional approach. In practice, I’ve found the flexibility to incrementally apply functional design where it makes sense extremely valuable—you might agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

&lt;p&gt;This and the &lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;previous post&lt;/a&gt; touched on state handling, but only at a high level as part of the shell. At the same time, the core needs to read and evolve that state.&lt;/p&gt;

&lt;p&gt;Designing clean interfaces for core functions requires a deeper understanding of how state is represented and passed. In the next post, I will take a closer look at state management and how to handle it effectively.&lt;/p&gt;




&lt;p&gt;Part of the &lt;em&gt;funkyposts&lt;/em&gt; blog — bridging object-oriented and functional thinking in C++.&lt;br&gt;
Created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v02)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Handling Side Effects in Modern C++: Designing Systems Around Pure Functions</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Thu, 26 Mar 2026 21:17:57 +0000</pubDate>
      <link>https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f</link>
      <guid>https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f</guid>
      <description>&lt;p&gt;&lt;em&gt;Using the functional core–imperative shell pattern to mitigate dependencies&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the previous &lt;a href="https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47"&gt;post&lt;/a&gt;, we saw why structuring business logic without side effects—using pure functions—effectively mitigates dependencies and makes code easier to reason about and test.&lt;/p&gt;

&lt;p&gt;But applying this idea in a real system isn't straightforward: Although you want your functions without side effects, the application you are building still must perform IO and maintain state to be useful.&lt;/p&gt;

&lt;p&gt;This leads to a practical design question: how do we structure systems so that side effects are decoupled from the business logic?&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoupling Logic and Side Effects
&lt;/h2&gt;

&lt;p&gt;The key idea is clear separation at the system level. So, instead of mixing business logic and side effects in the same place, one part of the system focuses on logic, while another is dedicated to side effect handling.&lt;/p&gt;

&lt;p&gt;In this setup the logic still determines which effects should happen, but without performing them directly. Instead, it describes what needs to happen, and the surrounding code interprets and performs the effects.&lt;/p&gt;

&lt;p&gt;If this reminds you of the command pattern, that’s not a coincidence—but here the idea is used to organize the entire system. And this is exactly the separation that structuring logic as pure functions naturally leads to.&lt;/p&gt;

&lt;p&gt;For example, a pure function decides to create a log message, so it returns a string and the caller interacts with the outside world by printing it to stderr.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Functional Core–Imperative Shell Pattern
&lt;/h2&gt;

&lt;p&gt;This simple structure is called &lt;em&gt;functional core–imperative shell&lt;/em&gt;: it splits an application into a functional core and an imperative shell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3uo581w1r5xh3sn860k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3uo581w1r5xh3sn860k.png" alt=" " width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;functional core&lt;/strong&gt;, is where the business logic is encoded in pure functions. You can wire these functions freely. This lets you build various layers of abstraction and organize the business logic cleanly.&lt;/p&gt;

&lt;p&gt;The remaining part forms the &lt;strong&gt;imperative shell&lt;/strong&gt; that calls the core and performs effects. For a C++ developer this is familiar terrain: using the standard library to write to stderr, mutating private members, using protocol stacks to communicate with other systems, or managing timers. In addition, the shell is responsible for managing the application’s execution environment, such as setting up concurrency. There is only one constraint: don’t implement business logic here.&lt;/p&gt;

&lt;p&gt;One important design aspect is the direction of dependencies: the shell depends on the core, but the core is independent of the shell. This enforces the clean separation as it prevents effects like state, IO, or other external concerns from leaking into the business logic and keeps the core self-contained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying the Pattern in C++
&lt;/h2&gt;

&lt;p&gt;Let's look at a simplified C++ example from a snake game to see core and shell clearly. Consider direction change validation — the rule that prevents instantly reversing direction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// functional core&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Direction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Down&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Right&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SoundEffect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InvalidInput&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;EvaluationResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;direction_changed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;SoundEffect&lt;/span&gt; &lt;span class="n"&gt;sound_effect&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="nf"&gt;opposite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Down&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Down&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="n"&gt;EvaluationResult&lt;/span&gt; &lt;span class="nf"&gt;evaluateDirectionChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;opposite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SoundEffect&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InvalidInput&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SoundEffect&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These functions and their input and output types form the &lt;em&gt;functional core&lt;/em&gt;. Here the business logic about the game's behavior on direction changes is encoded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// imperative shell&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;snake_direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;requested_direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;readUserInput&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// effectful&lt;/span&gt;

        &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evaluateDirectionChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snake_direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requested_direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// calling core&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sound_effect&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;SoundEffect&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;playSound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sound_effect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// effectful&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction_changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;snake_direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requested_direction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// effectful&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; function is the &lt;em&gt;imperative shell&lt;/em&gt;—it calls the core, interprets the result, and performs side effects like reading user input, playing sound, or mutating the game state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap - What has changed
&lt;/h2&gt;

&lt;p&gt;The separation into core and shell shifts how business logic is structured and how it interacts with the rest of the system. But let's state it more concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the business logic no longer performs effects directly but only decides what should happen&lt;/li&gt;
&lt;li&gt;side effect handling (IO, state mutation) is no longer distributed across the business logic but centralized outside of it&lt;/li&gt;
&lt;li&gt;state is no more hidden and implicitly mutated but visible at the top level and explicitly passed into the core and returned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these changes introduce an explicit interface between the pure business logic and the effectful parts of the system at the cost of introducing additional types to describe those effects.&lt;/p&gt;

&lt;p&gt;Looking at it through the lens of dependencies from the previous post, the situation improves as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the business logic has no dependencies on external systems&lt;/li&gt;
&lt;li&gt;fewer places in the system depend on IO or state mutation, and these dependencies are localized instead of spread across the system&lt;/li&gt;
&lt;li&gt;state dependencies are explicit: the business logic depends only on input data passed in and returned, not on internal mutable state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a consequence, reasoning about the business logic becomes significantly easier, and testing follows naturally: you call the function with input values and verify the result—without heavy mocking, dependency injection, or complex setup. This is exactly the improvement we were aiming for in the previous post.&lt;/p&gt;

&lt;p&gt;In addition, effect handling becomes easier to understand and control, as it is no longer spread across the logic but centralized in one place.&lt;/p&gt;

&lt;p&gt;So, the main achievement of &lt;em&gt;functional core–imperative shell&lt;/em&gt; is removing hidden dependencies from the business logic and localizing the remaining ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Small Works Well
&lt;/h2&gt;

&lt;p&gt;Although this is a fundamental shift in how to deal with side effects, it doesn’t require an all-or-nothing redesign. The pattern works for a subset of your business logic, so you can apply it locally, one part of the system at a time: start by moving effectful behavior out of a single piece of business logic while leaving the surrounding structure in place. From there, you can incrementally restructure more of the system step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leads Next
&lt;/h2&gt;

&lt;p&gt;All the side effect handling is now centralized in the shell. As the application grows this accumulates complexity there. Even in a simple snake game, this escalates quickly: handling user input, playing sound, managing the game state, and performing screen IO.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6"&gt;next post&lt;/a&gt;, I will show how to refine this design further to better organize the application to address scaling.&lt;/p&gt;




&lt;p&gt;Part of the &lt;em&gt;funkyposts&lt;/em&gt; blog — bridging object-oriented and functional thinking in C++.&lt;br&gt;
Created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v04)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bridging Object-Oriented and Functional Thinking in Modern C++</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Tue, 24 Mar 2026 22:15:00 +0000</pubDate>
      <link>https://dev.to/mahush/why-functional-programming-got-me-4h2p</link>
      <guid>https://dev.to/mahush/why-functional-programming-got-me-4h2p</guid>
      <description>&lt;p&gt;&lt;strong&gt;Re-published with major changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post has been significantly revised. Read the updated version &lt;a href="https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;This post previously explored combining object-oriented and functional programming in C++. However, the perspective has shifted—especially regarding how dependencies impact system design and testability.&lt;/p&gt;

&lt;p&gt;To avoid confusion, please refer to the updated version above, which reflects the current direction of this series.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
