<?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>Forem: Matti | funkyposts</title>
    <description>The latest articles on Forem by Matti | funkyposts (@mahush).</description>
    <link>https://forem.com/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>Forem: Matti | funkyposts</title>
      <link>https://forem.com/mahush</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mahush"/>
    <language>en</language>
    <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://forem.com/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6</link>
      <guid>https://forem.com/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6</guid>
      <description>&lt;p&gt;&lt;em&gt;How to structure growing C++ systems into multiple actor-driven core–shell pairs&lt;/em&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 separates code into a pure part where the business logic is implemented and an effectful part that calls the pure one and handles its side effects. In consequence, the shell must handle all side-effects of the application. &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. Too many unrelated concerns end up in one central hub, making the code harder to reason about and maintain.&lt;/p&gt;

&lt;p&gt;Which raises the crucial question: how do we scale the &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern?&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 responsibilities. Thus, a single shell isn’t enough—we need multiple ones, each focusing on a subset of side effects to be handled.&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.&lt;/p&gt;

&lt;p&gt;To put this idea into practice, the actor model is perfectly suited, thanks to its ability to encapsulate state and localize side effects. The idea 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 interact easily with one another. &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;However, 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;/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%2Ffx06ps2ve6d3oyrv87r2.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%2Ffx06ps2ve6d3oyrv87r2.png" alt=" " width="800" height="532"&gt;&lt;/a&gt;&lt;/p&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 Independent Module Interfaces&lt;/strong&gt;&lt;br&gt;
The input handling and game logic are completely decoupled. Each actor has a narrow, focused responsibility. 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: Localized Complexity&lt;/strong&gt;&lt;br&gt;
Complexity is distributed across multiple shells. Each shell only handles its own side effects. The &lt;code&gt;GameEngineActor&lt;/code&gt; doesn't care about publishing direction messages, and the &lt;code&gt;InputActor&lt;/code&gt; doesn't deal with the game state or the game loop timer. Each part remains small and understandable.&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 there is no shared mutable state and therefore no need for manual synchronization. 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;h2&gt;
  
  
  Key Advantage: Bridging by Design
&lt;/h2&gt;

&lt;p&gt;Actually, 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 functional and non-functional code via inter-actor communication. So, the same way actors implemented as shell-core pairs can interact with each other they can also interact with actors implemented in traditional OOP design. This is actually game-changing. You can build a single application that combines traditional OOP design with functional design, with clear boundaries between them. 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 you there. &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 structuring the system into multiple core–shell pairs. This decouples unrelated concerns like handling input and advancing game state. In addition, this actor-based separation enables a clean bridge between traditional object-oriented design and a functional approach.&lt;/p&gt;

&lt;p&gt;Sure, introducing actors 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, if you choose this architecture, you get a strong foundation for building modular applications while being able to bridge programming paradigms. 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 previous &lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;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 the underlying mechanics. 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; (v01)&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++: Interfacing Pure Functions with Our Imperative World</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Thu, 26 Mar 2026 21:17:57 +0000</pubDate>
      <link>https://forem.com/mahush/interfacing-pure-functions-with-our-impure-world-5e8f</link>
      <guid>https://forem.com/mahush/interfacing-pure-functions-with-our-impure-world-5e8f</guid>
      <description>&lt;p&gt;&lt;em&gt;Introducing the functional core–imperative shell pattern&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The previous &lt;a href="https://dev.to/mahush/why-functional-programming-got-me-4h2p"&gt;post&lt;/a&gt; clarified why structuring business logic as pure functions makes code easier to test and reason about. But when you try to utilize pure functions, you quickly run into a problem: Although you don't want your functions to have side effects, the application you are building still must perform effectful operations to be useful. These include IO, updating state, or reacting in a time‑based manner. This leads to a practical question: how can effect‑free functions ultimately cause the effects an application must perform?&lt;/p&gt;

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

&lt;p&gt;The high‑level answer is surprisingly simple: pure functions let someone else perform side effects for them. Therefore, they return data describing what should happen, and it's the caller that interprets the result and performs the effects. So, effect‑related behavior still happens—it’s just moved to the call site. For example, a pure function decides to create a log message, and the caller interacts with the outside world by printing it to stderr. &lt;/p&gt;

&lt;p&gt;If this reminds you of the command pattern, you’re not wrong—but here it’s about how you organize the system. The key idea is to introduce two complementary worlds: one where pure code requests effects and one where imperative code performs them.&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 (pure business logic) and an imperative shell (which calls the core and performs side effects).&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;&lt;strong&gt;Imperative Shell&lt;/strong&gt;: For a C++ developer this part is familiar. Here anything effectful is allowed: using the standard library to write to stderr, using protocol stacks to communicate with other systems, mutating private members to maintain state, or managing timers. In addition, the shell is responsible for managing the application’s execution environment, such as setting up concurrency. The only constraint is: don’t implement business logic here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functional Core&lt;/strong&gt;: Every business decision is encoded in pure functions. These functions together form the core. You can compose them freely, while these compositions remain pure. This lets you build various layers of abstraction and organize the business logic cleanly.&lt;/p&gt;

&lt;p&gt;One important design aspect I want to highlight: the core is self‑contained. So, while the shell depends on the core, the core is independent of the shell. This enables testing the core in complete isolation from any shell. &lt;/p&gt;

&lt;p&gt;This is exactly where utilizing pure functions pays off: testing becomes straightforward: you call the function with input values and verify the result. There’s no heavy mocking, no dependency injection, and no complex setup.&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="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="c1"&gt;// functional core&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;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;input&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;input&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;input&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;evaluateDirectionChange&lt;/code&gt; function is the functional core—it's pure, encoding the business rule about the game's behavior on direction changes. The &lt;code&gt;main&lt;/code&gt; function is the imperative shell—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;
  
  
  Starting Small Works Well
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern allows you to clearly distinguish between the imperative and the pure worlds, while having them bridged via simple function calls.&lt;/p&gt;

&lt;p&gt;Yes, this is a fundamental shift in how to deal with side effects. But this approach doesn’t invalidate your existing C++ design. Instead of starting from scratch, it's about shifting where effectful behavior takes place. And, it's not about all or nothing, in practice you can start small. The pattern works also for a subset of your business logic. Take a piece of code and split out the side effects. Already with the first step you gain better testability and reasoning for the resulting pure world code as described in the previous &lt;a href="https://dev.to/mahush/why-functional-programming-got-me-4h2p"&gt;post&lt;/a&gt;. Then incrementally push more side effects to the edges.&lt;/p&gt;

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

&lt;p&gt;The &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern works well for decoupling business logic from side effects, that problem is solved.&lt;/p&gt;

&lt;p&gt;But as applications grow, a new problem appears: the shell starts accumulating unrelated responsibilities. Even in a simple snake game, this escalates quickly: handling user input, playing sound, managing the game state, and performing screen IO all end up in the same place. &lt;/p&gt;

&lt;p&gt;In my next post, I will show how to refine this design further so these responsibilities can be separated cleanly as well.&lt;/p&gt;




&lt;p&gt;Part of the &lt;em&gt;funkyposts&lt;/em&gt; blog — blogging to bridge traditional C++ and functional programming by exploring how functional patterns and architectural ideas can be applied in modern C++. 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; (v03)&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://forem.com/mahush/why-functional-programming-got-me-4h2p</link>
      <guid>https://forem.com/mahush/why-functional-programming-got-me-4h2p</guid>
      <description>&lt;p&gt;&lt;em&gt;Why functional programming got me — and the design changes that followed&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;C++ development is still heavily shaped by object-oriented thinking. In real-world projects, this can create friction—especially around testing and tight coupling. At the same time, modern C++ has evolved into a multi-paradigm language with increasingly strong support for functional programming. But having the tools doesn’t automatically change how we design systems. In this post, I will explore what actually changes when you start combining object-oriented and functional thinking in C++, based on the experiences that led me here. &lt;/p&gt;

&lt;h2&gt;
  
  
  My Early Days
&lt;/h2&gt;

&lt;p&gt;In the first years of my career I aimed to master OOP as provided by C++98. What I knew about programming at that time brought me to the conclusion that writing high quality code is just a matter of being fluent in the language, the object-oriented paradigm and the design patterns around it. So, I was practicing, and as the years passed and my ability to write idiomatic object-oriented code improved I began to see its limits.&lt;/p&gt;

&lt;p&gt;To better understand my journey here, let me share another complementary learning path of mine that is about testing. The first embedded projects I joined didn’t have any automated tests. Instead, testing was done manually by using the device, observing its behavior and pressing its buttons. We developers did this ourselves to see if our changes worked and a dedicated test department did so all day to “ensure” the device generally behaves as expected. In a later embedded project two colleagues were practicing unit testing for their production code. Given how many regressions we had experienced and how much effort it took to fix them in coordination with the test department, I realized the potential of automated tests. So, I started writing unit tests myself. At that time, the OOP patterns I was applying became limiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I Struggle with OOP
&lt;/h2&gt;

&lt;p&gt;When diving into unit testing, an essential learning was: If you want to unit test your code, you actually must write your code to be unit-testable. In short, you must be able to execute smaller parts of your code in isolation. That is applying 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. Instead of real dependencies, the test passes its mocks as constructor arguments.&lt;/p&gt;

&lt;p&gt;&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;&lt;strong&gt;Interfaces create tight coupling between alternative implementations&lt;/strong&gt;: Mocks must implement the same interface as the real components they replace. These interfaces often 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;&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 unnecessary complexity and makes tests more brittle.&lt;/p&gt;

&lt;p&gt;Kudos to Eric Elliott who wrote 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; that helped me gain a better understanding of these issues and how functional programming allows us to do better. This really got me. My interest in functional programming was born.&lt;/p&gt;

&lt;p&gt;Looking at OOP from an FP perspective also made another long-standing issue clear: inheritance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inheritance creates tight coupling within the hierarchy&lt;/strong&gt;: Once a class hierarchy is established, the base class interface becomes almost impossible to change without impacting every derived class. Even small adjustments propagate through the hierarchy, making class hierarchies rigid and expensive to evolve.&lt;/p&gt;

&lt;p&gt;So, these were my pain points, and I was primed to find solutions in functional programming and I did.&lt;/p&gt;

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

&lt;p&gt;The key idea is to start thinking in terms of pure functions alongside classes. A pure function is as simple as it gets: it takes input and returns output, with no hidden state and no implicit dependencies. Using pure functions as building blocks unlocks an additional solution space. For example, the issues described above evolve as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testability improves&lt;/strong&gt;: This makes testing straightforward, you call the function with test data and check the result. No hidden state means no complicated setup, and no implicit dependencies mean you don’t need objects standing in for other objects. You may need to supply a function as an argument when testing. But this “mock” is just a single, stateless function, not an entire class with multiple methods and internal state. The complexity drops dramatically. So the takeaway is this: when you structure your logic as pure functions, the heavy mocking simply disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coupling decreases&lt;/strong&gt;: Of course, class hierarchy coupling also goes away with FP, functionality still builds on top of other functionality, but when this essentially means composing pure functions, all dependencies are only at the function signature level which again reduces complexity.&lt;/p&gt;

&lt;p&gt;In other words, when you compose your business logic out of pure functions, the design becomes cleaner, improving both testability and maintainability. Don't get me wrong, this is not about replacing OOP with FP, it's about complementing where appropriate.&lt;/p&gt;

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

&lt;p&gt;Sure thing, learning a new programming paradigm takes sustained 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 make functional programming feel more approachable. My original motivation wasn’t to blog about functional programming. I simply wanted to figure out how to apply its ideas effectively in real-world C++ code. After exploring this for a while and finding techniques that work for me, it feels natural to share and discuss them. The funkyposts blog is my attempt to build a bridge between traditional C++ and functional programming by illustrating practical FP patterns in modern C++. Hopefully this helps other C++ developers go more functional while inviting constructive feedback on my own understanding.&lt;/p&gt;

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

&lt;p&gt;As said, I advocate for implementing the business logic, the heart of an application in a functional style, while utilizing traditional C++ for everything else. The subsequent posts in this series will show how to structure an application accordingly.&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;Interfacing Pure Functions with Our Impure World&lt;/a&gt;&lt;/strong&gt; — How to structure applications using the functional core–imperative shell architecture&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 — blogging to bridge traditional C++ and functional programming by exploring how functional patterns and architectural ideas can be applied in modern C++. 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; (v05)&lt;/p&gt;

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