<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[A lightw8, learning new things, together]]></title><description><![CDATA[A lightw8, learning new things, together]]></description><link>https://lightw8.blog</link><generator>RSS for Node</generator><lastBuildDate>Mon, 20 Apr 2026 15:49:34 GMT</lastBuildDate><atom:link href="https://lightw8.blog/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Firewalla Purple, 2.5 GbE, and Wi-Fi 6 APs: A late-2021 home + work networking setup that won't break the bank (or your spirit)]]></title><description><![CDATA[I wonder if others share my self-contradiction: while I love learning about new shiny technologies, when it comes to adopting technologies at home, I'm kind of a laggard. Relatively speaking. Which is why it's taken me over 10 years living at my curr...]]></description><link>https://lightw8.blog/firewalla-purple-25-gbe-and-wi-fi-6-aps-a-late-2021-home-work-networking-setup-that-wont-break-the-bank-or-your-spirit</link><guid isPermaLink="true">https://lightw8.blog/firewalla-purple-25-gbe-and-wi-fi-6-aps-a-late-2021-home-work-networking-setup-that-wont-break-the-bank-or-your-spirit</guid><category><![CDATA[networking]]></category><category><![CDATA[router]]></category><category><![CDATA[smart home]]></category><category><![CDATA[hardware]]></category><dc:creator><![CDATA[David Cuccia]]></dc:creator><pubDate>Sun, 10 Oct 2021 21:16:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633899150590/vtBY8ALbz.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I wonder if others share my self-contradiction: while I love learning about new shiny technologies, when it comes to adopting technologies at home, I'm kind of a laggard. Relatively speaking. Which is why it's taken me over 10 years living at my current place to upgrade my home network. I'd like to share my journey to finally taking the plunge. If you're considering something similar, hopefully there's a helpful tidbit or two in here to help you along as well.</p>
<h3 id="heading-the-malaise-era-the-good-enough-isp-router-solution">The Malaise Era: The "good enough" ISP router solution</h3>
<p>In general, I'm absolutely a neophyte: I love learning about and building new things. I'm CTO of a medical device company, my favorite hobby is learning about new software practices and languages, and once, in order to learn microcontroller programming, I created a weight sensor that would count the number of "sessions" my cat had in the litterbox and send me an email when it reached a threshold count (not going to admit what that threshold count was...).</p>
<p>But when it's come to adopting the cutting edge at home, more often than not I find myself waiting unitil someone can polish those sharp edges before adopting them myself. While I'm pretty good at solving tech issues, I'd rather be able to treat my home appliances as appliances, and save my tech support stamina for the day job.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633824355748/NQFM5SO9i.png" alt="clipart4691060.png" /></p>
<p>That's why, for home networking, I've always gravitated to a simple "set it and forget it" setup: the standard all-in-one Wi-Fi modem-router-firewall-switch that comes out of the box from the ISP. A couple years ago, I was feeling fancy and added a mesh network to improve coverage out in the yard and in my back office. But still quite vanilla. I was intrigued with trying DD-WRT/Tomato/Open WRT router firmware but didn't really see the cost-benefit of having to maintain it correctly and securely. (This is probably a fallacy - I'm sure there are good examples where I would have been more secure with one of the above than using/trusting my router's commercial firmware. But at least superficially, the default setup seemed like a "solved problem" - why mess with it?)</p>
<p>But recently, I was working on a side-project to set up a Raspberry Pi nature cam in my backyard (more on that someday...), and had to do some "simple" port-forwarding that proved inordinately difficult and confusing. The real issue was hard to pin down—it ended up being somehow related to a "double-NAT" issue between the ISP router, the Google WiFi mesh, and my ISP's DNS (as Scott Hanselman says, "<a target="_blank" href="https://twitter.com/shanselman/status/1124072937018077184">It's</a> <a target="_blank" href="https://twitter.com/shanselman/status/1285820992040734720">always</a> <a target="_blank" href="https://twitter.com/shanselman/status/1445055795507253253">DNS</a>")—but, it took close to a full day to resolve, and ultimately the solution was to turn off the ISP router's DHCP and firewall altogether. This worked, but it was both limiting and unsatisfying, as I had to hang my entire network off the Google Mesh, which is quite bare-bones when it comes to router features and customization.</p>
<p>Based on the above and similar experiences, along with seeing some new products come on the market, I found myself interested to see if I could add faster performance and new capabilities to my network, without breaking the bank, requiring learning major sysadmin skills, or requiring significant routine maintenance.</p>
<h3 id="heading-requirements">Requirements</h3>
<ol>
<li><p><strong>Partitioned network</strong> One of the things I really wanted to customize was to partition and monitor my network into different profiles: guest access, general device access, minimal access for IoT devices like the nature cam, and machines I use for work. In particular, it would have been be great to have a partitioned "virtual network" for my work machines that could 1) isolate traffic more securely, to minimize exposure to my company's network when I access office/cloud resources via VPN, and 2) potentially provide a persistent VPN connection to those resources, versus continually having to use software clients for that.</p>
</li>
<li><p><strong>Faster connectivity</strong> I also wanted a faster 2.5GbE hard-wired connection and modern Wi-Fi 6 wireless access for my back office (neither of which was available via the Google mesh system), and a firewall VPN that could support the bandwidth of my 1-gigabit internet fiber internet line (I was currently limited to software clients to achieve this, and the connection reliability and performance were surprisingly device dependent).</p>
</li>
<li><p><strong>Network storage</strong> Finally, it would be nice to take advantage of a 2.5GbE (or even a minimal 10GbE) "backbone" and have a high-speed local NAS to house local copies of all our valuable data (in addition to their cloud counterparts currently in Dropbox Family and Google Photos). I didn't want to splurge for multi-thousand-dollar commercial solutions for this, but the OSS storage world is becoming <a target="_blank" href="https://www.truenas.com/truenas-core/">quite mature</a>, and the commodity hardware available for DIY solutions are getting <a target="_blank" href="https://githubmemory.com/repo/geerlingguy/raspberry-pi-pcie-devices/issues/202">very interesting</a>.</p>
</li>
</ol>
<h3 id="heading-initial-firewall-research-a-goldilocks-problem">Initial Firewall Research: A Goldilocks problem</h3>
<p>After researching (read: mostly watching lots of YouTube videos and the occasional Reddit thread—thanks <a target="_blank" href="https://www.youtube.com/c/ServeTheHomeVideo">ServeTheHome</a>, <a target="_blank" href="https://www.youtube.com/c/JeffGeerling">Jeff Geerling</a>, <a target="_blank" href="https://www.youtube.com/user/NetworkChuck">NetworkChuck</a>, <a target="_blank" href="https://www.youtube.com/user/TheTecknowledge">Lawrence Systems</a>), it seemed like the prevailing wisdom on evolving my firewall/router system was to either go with a "prosumer" <a target="_blank" href="https://store.ui.com/collections/unifi-network-unifi-os-consoles/products/unifi-dream-machine">Unifi Dream Machine</a> solution, or to run pfSense on my own hardware (Netgate <a target="_blank" href="https://www.netgate.com/pfsense-plus-software/how-to-buy#appliances">sells pfSense hardware, too</a>, but gigabit-capable VPN appliances get expensive).</p>
<p>Neither exactly fit the bill. On one hand, the Dream Machine was a nice commercial solution, but while Ubiquity has something of a cult following, they tend to have mixed reviews on how they update their products. Also, I didn't need the Wi-Fi of the Dream Machine, since I wanted to have a separate wireless solution (see below). Here's a video showing Network Chuck's infatuation with the thing:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=BezoNUflqXo">https://www.youtube.com/watch?v=BezoNUflqXo</a></div>
<p> </p>
<p>On the other hand, while pfSense seemed quite full featured (and an interesting learning opportunity), I didn't feel like I could "dabble" and still feel like I was creating a reliable and secure appliance that I could learn in a weekend and leave alone for months at a time. And since I needed some decent hardware VPN performance, I couldn't run a simple Raspberry Pi or similar single-board computer solution. Constructing and maintaining a full set of server hardware was going to be both expensive and error-prone (see Lawrence Systems' <a target="_blank" href="https://youtu.be/fsdm5uc_LsU">video explainer</a> on selecting the right hardware and details on virtualization performance for pfSense). Might as well spring for the $699 Netgate 6100 and be done with it...but, ouch that's a bit expensive:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=5AidO5Zj0Yo">https://www.youtube.com/watch?v=5AidO5Zj0Yo</a></div>
<p> </p>
<h3 id="heading-firewalla-purple-a-just-right-user-friendly-and-feature-rich-gigabit-firewall-router-and-vpn">Firewalla Purple: A "just right" user friendly and feature-rich gigabit firewall, router, and VPN</h3>
<p>Enter <a target="_blank" href="https://firewalla.com/">Firewalla</a>, a company that has been innovating to democratize enterprise-level firewall capabilities that are simultaneously affordable and user-friendly. Beyond the great feature set, ease of configuration, and polished mobile software client, the thing that probably impressed me the most was their track record of responding to customer feedback and continuing to evolve their hardware solution to achieve high performance on low-cost SBC platforms. After reading a bit and comparison shopping, I was ready to bite the bullet and purchase their <a target="_blank" href="https://firewalla.com/products/firewalla-gold">Firewalla Gold</a> for $439. I didn't necessarily need the gigabit switch included in the package, but it seemed to be the right combo of software and hardware features otherwise.</p>
<p>Luckily, I sat on this decision for a minute. Because while I was deciding to commit, they launched <a target="_blank" href="https://www.indiegogo.com/projects/firewalla-purple-gigabit-cyber-security#/">Firewalla Purple</a>—a $299 step-down option with everything I needed, and nothing I didn't. Solved.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/gE6VtOv69cU">https://youtu.be/gE6VtOv69cU</a></div>
<p> </p>
<p>One of the nice things about Firewalla Purple is that it's very much based on the Gold. And so once any beta software stack issues are resolved, one can expect to have a very similar, full-featured experience as their other products, which tend to have great reviews. I'll be sure to follow-up as things stabilize with any concerns or gotchas I discover.</p>
<h3 id="heading-the-rest">The Rest</h3>
<p>Once I had the firewall "keystone" in place, the rest was downhill. Below, I detail what I used to round out my network. Note: prices are what I paid in July 2021 - as of writing this, the chip shortage has caused current prices to surge higher. I'm using my original prices with the hopes they're more reflective of long-term pricing.</p>
<h4 id="heading-qnap-10g25g-network-switch">QNAP 10G/2.5G Network Switch</h4>
<p>The next piece of the puzzle was the main switch to receive the output of the Firewalla Purple. I ended up splurging on this $339 QNAP <a target="_blank" href="https://www.amazon.com/gp/product/B097G9RS9T">QSW-M2108-2C</a> Layer 2 managed switch with 2x10G SFP+/RJ45 combo ports and 8x2.5G ports. This gives me 2.5G for all my downstream devices, a fast 10G "backbone" that I can use to connect my back office to the main house, an additional 10G line that I can use to attach a future DIY NAS, and VLAN capabilities to partition my devices on separate networks as needed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633825706790/qhujk_Zz9.jpeg" alt="qnap switch.jpg" /></p>
<h4 id="heading-netgear-10g1g-network-switch">Netgear 10G/1G Network Switch</h4>
<p>For now, I've run a Cat 7 network cable to the back office, so a $199 Netgear <a target="_blank" href="https://www.amazon.com/gp/product/B076642YPN">GS110MX</a> 10-Port unmanaged switch (8x1G, 2x10G/2.5G) in the back office does the job, allowing my main machine to connect above 1Gbps. This was a compromise in cost, but it's not fantastic value - I feel like the cost of this unit should be $50 cheaper as-is, or add $50 and upgrade the 1G channels to 2.5G. I expect since commodity 2.5G networking is starting to take off, we'll see more affordable options soon.</p>
<h4 id="heading-unifi-wifi-6-long-range-access-points">Unifi WiFi 6 Long-Range Access Points</h4>
<p>Two $179 <a target="_blank" href="https://store.ui.com/products/unifi-6-long-range-access-point">U6-LR-US</a> access points cover our whole property well: one at the center of the house and one for the back office.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633900941454/NEQemXYNR.png" alt="unifi ap v2.png" /></p>
<h4 id="heading-netgear-poe-5-port-managed-gigabit-switches">Netgear PoE+ 5-port Managed Gigabit Switches</h4>
<p>Maybe overkill for just powering the access points (vs buying a PoE injector for each), but these two $49 <a target="_blank" href="https://www.amazon.com/gp/product/B08LR18SC4">GS305EP</a> 5-port managed PoE+ switches give me the ability to add other home automation and/or video camera devices in the future. Getting the managed versions will allow me to toggle power to those devices remotely, versus having to physically access the hardware.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633825051596/B8MtkL26d.jpeg" alt="netgear poe.jpg" /></p>
<h4 id="heading-sabrent-25gbps-usb-c-adapters">Sabrent 2.5Gbps USB-C adapters</h4>
<p>For a surprisingly-affordable $35, <a target="_blank" href="https://www.amazon.com/gp/product/B08979LXJK">Sabrent NT-S25G</a> USB-C to 2.5GbE adapters are quick and quiet. Assuming they continue to prove reliable, they will be my go-to for most systems. (Sabrent also offer a 10G Thunderbolt 3 option for $160, for those machines without a <a target="_blank" href="https://www.amazon.com/10Gtek-Ethernet-Converged-Network-Adapter/dp/B01LWU8BEB">PCI-E NIC option</a>)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633896836898/PZoa_ax9k.jpeg" alt="sabrent.jpg" /></p>
<h3 id="heading-wrapping-up">Wrapping up</h3>
<p>At the end of the day, I now have a brand new managed 10G/2.5G + Wi-Fi 6 home networking setup with a sophisticated and user-friendly firewall, PoE+ smart device expansion options, and room for a high-performance DIY home NAS upgrade, all for the price of an entry-level MacBook Pro. Pretty happy with the value here. As a caveat, I'm definitely still in the honeymoon period, so I'll be sure to update as things progress.</p>
<p>Hope this walk-through was helpful! What hardware/software combos do you use? I'd be very interested in hearing from other people's expertise and experiences down in the comments below.</p>
<p>Thanks for reading, and talk soon. David</p>
]]></content:encoded></item><item><title><![CDATA[Railway Oriented Programming III]]></title><description><![CDATA[The first post in this series on ROP covered the high-level concepts of the approach, and then I implemented some code building blocks that allow us to promote functions of various shapes into composable 2-track functions.
Here I'd like to share some...]]></description><link>https://lightw8.blog/railway-oriented-programming-iii</link><guid isPermaLink="true">https://lightw8.blog/railway-oriented-programming-iii</guid><category><![CDATA[Functional Programming]]></category><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><category><![CDATA[error handling]]></category><dc:creator><![CDATA[David Cuccia]]></dc:creator><pubDate>Tue, 29 Jun 2021 17:16:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624817043645/bUaWzXZ2V.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <a target="_blank" href="https://lightw8.blog/railway-oriented-programming">first post</a> in this series on ROP covered the high-level concepts of the approach, and then I <a target="_blank" href="https://lightw8.blog/railway-oriented-programming-ii">implemented some code building blocks</a> that allow us to promote functions of various shapes into composable 2-track functions.</p>
<p>Here I'd like to share some simple code samples that exercise these ideas. If you'd like, you can follow along by browsing the code samples in my repo here: https://github.com/lightw8/rop-examples-csharp</p>
<p>First off, after a little refactoring and paring down a bit<sup>1</sup>, here's the sum total of the ROP helpers from last time, along with a <code>Compose</code> extension method to chain functions together:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">RailwayOrientedProgramming</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Error</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Error</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message</span>)</span> =&gt; Message = message;
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Message { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">string</span> <span class="hljs-title">ToString</span>(<span class="hljs-params"></span>)</span> =&gt; Message;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">T</span>&gt; : <span class="hljs-title">Choice</span>&lt;<span class="hljs-title">T</span>, <span class="hljs-title">Error</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">T <span class="hljs-keyword">value</span></span>)</span> =&gt; <span class="hljs-keyword">new</span> Result&lt;T&gt;(<span class="hljs-keyword">value</span>);
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">Error error</span>)</span> =&gt; <span class="hljs-keyword">new</span> Result&lt;T&gt;(error);

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Result</span>(<span class="hljs-params">T item</span>) : <span class="hljs-title">base</span>(<span class="hljs-params">item</span>)</span> { }
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Result</span>(<span class="hljs-params">Error item</span>) : <span class="hljs-title">base</span>(<span class="hljs-params">item</span>)</span> { }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Choice</span>&lt;<span class="hljs-title">A</span>, <span class="hljs-title">B</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Choice</span>(<span class="hljs-params">A item</span>)</span> { Item = item!; }
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Choice</span>(<span class="hljs-params">B item</span>)</span> { Item = item!; }

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">dynamic</span> Item { <span class="hljs-keyword">get</span>; }

        <span class="hljs-comment">// equality and string representation delegated to member (Item)</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">Equals</span>(<span class="hljs-params"><span class="hljs-keyword">object</span>? obj</span>)</span> =&gt; Item.Equals(obj);
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">int</span> <span class="hljs-title">GetHashCode</span>(<span class="hljs-params"></span>)</span> =&gt; Item.GetHashCode();
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">string</span> <span class="hljs-title">ToString</span>(<span class="hljs-params"></span>)</span> =&gt; Item.ToString();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RopExtensions</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-title">Func</span>&lt;<span class="hljs-title">T1</span>, <span class="hljs-title">T3</span>&gt; <span class="hljs-title">Compose</span>&lt;<span class="hljs-title">T1</span>, <span class="hljs-title">T2</span>, <span class="hljs-title">T3</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> Func&lt;T1, T2&gt; func1, Func&lt;T2, T3&gt; func2</span>)</span> =&gt;
            <span class="hljs-keyword">value</span> =&gt; func2(func1(<span class="hljs-keyword">value</span>));

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-title">Func</span>&lt;<span class="hljs-title">Result</span>&lt;<span class="hljs-title">TIn</span>&gt;, <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TOut</span>&gt;&gt; <span class="hljs-title">Bind</span>&lt;<span class="hljs-title">TIn</span>, <span class="hljs-title">TOut</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> Func&lt;TIn, Result&lt;TOut&gt;&gt; func</span>)</span> =&gt;
            choice =&gt; choice.Item <span class="hljs-keyword">switch</span>
            {
                Error e =&gt; (Result&lt;TOut&gt;)e, <span class="hljs-comment">// ugly cast because C#</span>
                TIn tIn =&gt; func(tIn).Item <span class="hljs-keyword">switch</span>
                           {
                               Error eOut =&gt; eOut,
                               TOut tOut  =&gt; tOut,
                               _          =&gt; <span class="hljs-keyword">new</span> Error(<span class="hljs-string">$"Invalid argument"</span>)
                           },
                _       =&gt; <span class="hljs-keyword">new</span> Error(<span class="hljs-string">$"Invalid argument"</span>)
            };

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-title">Func</span>&lt;<span class="hljs-title">Result</span>&lt;<span class="hljs-title">TIn</span>&gt;, <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TOut</span>&gt;&gt; <span class="hljs-title">Map</span>&lt;<span class="hljs-title">TIn</span>, <span class="hljs-title">TOut</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> Func&lt;TIn, TOut&gt; func</span>)</span> =&gt;
            Bind&lt;TIn, TOut&gt;(input =&gt; func(input)); <span class="hljs-comment">// implicit upcast of return type of func to Result&lt;TOut&gt;</span>

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-title">Func</span>&lt;<span class="hljs-title">TIn</span>, <span class="hljs-title">TIn</span>&gt; <span class="hljs-title">Tee</span>&lt;<span class="hljs-title">TIn</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> Action&lt;TIn&gt; deadEndFunction</span>)</span> =&gt;
            input =&gt; { deadEndFunction(input); <span class="hljs-keyword">return</span> input; };

        <span class="hljs-comment">// Removed, as it's the same signature as Tee (with Result&lt;TIn&gt; as the TIn type)</span>
        <span class="hljs-comment">// public static Func&lt;Result&lt;TIn&gt;, Result&lt;TIn&gt;&gt; Audit&lt;TIn&gt;(this Action&lt;Result&lt;TIn&gt;&gt; deadEndFunction) =&gt;</span>
            <span class="hljs-comment">// input =&gt; { deadEndFunction(input); return input; }; // perform the action and return the input</span>
    }
}
</code></pre>
<p>And now, let's contrive some overly-simple examples to exercise the helpers, using the minimalistic style of "top-level statements," a <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#top-level-statements">new capability</a> in C# 9.</p>
<p>I've created five separate functions with different signatures: two error-generating functions (<code>maybeAdd3</code> and <code>maybeInverse</code>), one simple function, expected to always succeed (<code>subtract7</code>), and two "audit" functions that either perform an action on simple "1-track" data value (<code>printFloats</code>) or on a 2-track <code>Result</code> type (<code>printFinalResult</code>).</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;
<span class="hljs-keyword">using</span> System.Linq;
<span class="hljs-keyword">using</span> RailwayOrientedProgramming;

<span class="hljs-comment">// 1) error-generating switch function</span>
Func&lt;<span class="hljs-keyword">int</span>, Result&lt;<span class="hljs-keyword">long</span>&gt;&gt; maybeAdd3 = val =&gt; val <span class="hljs-keyword">switch</span>
{
    <span class="hljs-number">13</span> =&gt; <span class="hljs-keyword">new</span> Error(<span class="hljs-string">"Unlucky!"</span>),
    _  =&gt; (Result&lt;<span class="hljs-keyword">long</span>&gt;)(val + <span class="hljs-number">3</span>)
};

<span class="hljs-comment">// 2) simple 1-track function</span>
Func&lt;<span class="hljs-keyword">long</span>, <span class="hljs-keyword">float</span>&gt; subtract7 = val =&gt; val - <span class="hljs-number">7f</span>;

<span class="hljs-comment">// 3) 1-track "dead-end" action (no return)</span>
Action&lt;<span class="hljs-keyword">float</span>&gt; printFloats = val =&gt; Console.Write(<span class="hljs-string">$"The value is currently <span class="hljs-subst">{val}</span>.\t"</span>);

<span class="hljs-comment">// 4) error-generating inverse function</span>
Func&lt;<span class="hljs-keyword">float</span>, Result&lt;<span class="hljs-keyword">double</span>&gt;&gt; maybeInverse = val =&gt; val <span class="hljs-keyword">switch</span>
{
    <span class="hljs-number">0f</span> =&gt; <span class="hljs-keyword">new</span> Error(<span class="hljs-string">"Inverse!"</span>),
    _  =&gt; (Result&lt;<span class="hljs-keyword">double</span>&gt;)(<span class="hljs-number">1f</span> / val)
};

<span class="hljs-comment">// 5) 2-track "dead-end" action (no return)</span>
Action&lt;Result&lt;<span class="hljs-keyword">double</span>&gt;&gt; printFinalResult = result =&gt;
{
    Action action = result.Item <span class="hljs-keyword">switch</span>
    {
        <span class="hljs-keyword">double</span> d =&gt; () =&gt; Console.WriteLine(<span class="hljs-string">$"Happy path! Final value is <span class="hljs-subst">{d}</span>"</span>),
        Error e  =&gt; () =&gt; Console.WriteLine(<span class="hljs-string">$"Error path :( (<span class="hljs-subst">{e.Message}</span>)"</span>)
    };
    action();
};

<span class="hljs-keyword">var</span> compositeFunc = maybeAdd3         <span class="hljs-comment">// 1) error-generating function</span>
    .Compose(subtract7.Map())         <span class="hljs-comment">// 2) map a simple 1-track function</span>
    .Compose(printFloats.Tee().Map()) <span class="hljs-comment">// 3) tee and map a simple dead-end action</span>
    .Compose(maybeInverse.Bind())     <span class="hljs-comment">// 4) bind error-generating inverse function</span>
    .Compose(printFinalResult.Tee()); <span class="hljs-comment">// 5) tee a 2-track dead-end action</span>

Console.WriteLine(<span class="hljs-string">"\nOutput:\n"</span>);
<span class="hljs-keyword">var</span> results = Enumerable.Range(<span class="hljs-number">0</span>, <span class="hljs-number">16</span>) <span class="hljs-comment">// 0,1,2,...,15</span>
    .Select(compositeFunc)            <span class="hljs-comment">// perform composite operation</span>
    .ToArray();                       <span class="hljs-comment">// force enumeration</span>
</code></pre>
<p>For each of the methods, I use one or more of the adapter methods to shape the inputs and outputs into the 2-track railway model. Notice:</p>
<ol>
<li><p>that <code>Bind</code> isn't needed at the beginning for <code>maybeAdd3</code> because it's starting with a simple <code>int</code>, not a 2-track <code>Result</code></p>
</li>
<li><p>how <code>Map</code> "lifts" the simple signature into the <code>Result</code> domain</p>
</li>
<li><p>how <code>Tee</code> is used to intercept the chain and perform actions on the intermediate values</p>
</li>
<li><p>that if we were to output the result (e.g. what you'd do at an API boundary), we would do something similar to <code>printFinalResult</code>, where we'd have to decide how to "roll-up" the two tracks into one</p>
</li>
</ol>
<p>Here's the output of the code:</p>
<pre><code class="lang-console">Output:

The value is currently -4.      Happy path! Final value is -0.25
The value is currently -3.      Happy path! Final value is -0.3333333432674408
The value is currently -2.      Happy path! Final value is -0.5
The value is currently -1.      Happy path! Final value is -1
The value is currently 0.       Error path :( (Inverse!)
The value is currently 1.       Happy path! Final value is 1
The value is currently 2.       Happy path! Final value is 0.5
The value is currently 3.       Happy path! Final value is 0.3333333432674408
The value is currently 4.       Happy path! Final value is 0.25
The value is currently 5.       Happy path! Final value is 0.20000000298023224
The value is currently 6.       Happy path! Final value is 0.1666666716337204
The value is currently 7.       Happy path! Final value is 0.1428571492433548
The value is currently 8.       Happy path! Final value is 0.125
Error path :( (Unlucky!)
</code></pre>
<p>In the end, there are 12 fully-processed values, and two errors (one because we hit the unlucky value 13, and one for an unallowed inverse operation). Also, note the side-effect of printing the value 0 succeeds, even though the final result is an <code>Error</code>, which occurs later on. ALL inputs, however, propagate through to the end, regardless. That's quite satisfying. I haven't included exception-to-error mapping, but ultimately those functions would end up looking just like the "maybe" functions.</p>
<p>A few nits:</p>
<ol>
<li><p>I ended up demonstrating this with <code>Func</code>-type functions, but this would work with methods as well.</p>
</li>
<li><p>In terms of readability, I felt being able to call extension methods on the <code>Func</code>s directly was more concise/clear to illustrate the point, but this would work equally well by calling the fully-qualified ROP helpers with method delegates.</p>
</li>
<li><p>Scott Wlaschin points out that you can also hide the "ROP-ified" functions behind a new namespace, so you can use the same names as before, where the extension methods aren't visible at the top-level.</p>
</li>
<li><p>Compared with F#, where function composition and pipe operators built into the language, <code>Compose</code> is a bit annoying. It would be great to have these in the language. I've always thought C# was a bit too restrictive with operator overloading, and it would be powerful to be able to define these ourselves and provide for this kind of functionality. But, I see how the language could get messy quickly if they let us expand the operators willy-nilly. Functions are becoming more and more like first-class citizens in C#, though, so maybe we'll also see some new features or operators emerge around composing them, too.</p>
</li>
</ol>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>I haven't yet exercised this model in any production code, but there are certainly some very nice qualities about it, particularly in the high-level clarity of the code's intent, and how the error approach nudges you toward TDD-style development where the errors get explicitly handled and handed off to an appropriate consumer.</p>
<p>One of the places it might be most appealing is in massively parallel compute code, where you don't want to break the propagation for spurious edge cases (that yield NaN, for example), or where you want to skip computation at various points if the values fall outside of certain tolerances or desired ranges. On the flipside, there is a significant amount of abstraction happening to achieve this functional model, and so we lose the tight data-locality and compiler optimizations you'd get from classic "for-loop" iteration and direct application of logic, versus the functional parameters passed in. The provided <code>Choice</code> type is also a bit heavyweight, as it requires runtime dynamic computations. I'd like to do some more reading to understand how functional programmers have worked toward these high-performance scenarios.</p>
<p>In any case, it's been a fun journey to work through this talk. Thanks to Mr. Wlaschin for the clear, interesting, useful, and funny [presentation](favorite talks). What are your thoughts about this style of programming? What would be your major barriers to adopting this in your code?</p>
<p>Thanks for reading, and talk soon. David</p>
<p><sup>1 </sup> I ultimately removed <code>Audit</code>, as it shares the same generic signature as Tee. That is, at least how I'm understanding the talk. Haven't reviewed other work on the topic...not yet, at least!</p>
]]></content:encoded></item><item><title><![CDATA[Railway Oriented Programming II]]></title><description><![CDATA[In the last post, I talked at a high-level about Scott Wlaschin's "Railway Oriented Programming" (ROP)1  analogy for applying a functional style to things like error checking and exception handling.
Here, I'd like to enumerate some of the code sample...]]></description><link>https://lightw8.blog/railway-oriented-programming-ii</link><guid isPermaLink="true">https://lightw8.blog/railway-oriented-programming-ii</guid><category><![CDATA[Functional Programming]]></category><category><![CDATA[C#]]></category><category><![CDATA[error handling]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[David Cuccia]]></dc:creator><pubDate>Tue, 15 Jun 2021 17:41:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1622926680493/ChXz8zVtG.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the <a target="_blank" href="https://lightw8.blog/railway-oriented-programming">last post</a>, I talked at a high-level about Scott Wlaschin's "Railway Oriented Programming" (ROP)<sup>1 </sup> analogy for applying a functional style to things like error checking and exception handling.</p>
<p>Here, I'd like to enumerate some of the code samples I built in C# as I "coded along" at home. I haven't yet compared these to any other online code samples on the topic, but you can check my work and see some extra resources on the topic on his ROP page <a target="_blank" href="https://fsharpforfunandprofit.com/rop/">here</a>. There, he also issues a warning that this approach isn't always recommended, and has <a target="_blank" href="https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/">some downsides</a>, too. I'll leave that analysis for another day.</p>
<h2 id="heading-error-generating-functions">Error-generating functions</h2>
<p>An error-generating function acts as a "switch." A <code>TIn</code> input yields either a success type <code>TOut</code> output (the normal "happy path" output), or an <code>Error</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-function">SuccessOrError <span class="hljs-title">ErrorGeneratingFunction</span>(<span class="hljs-params">TIn input</span>)</span> { ... }
</code></pre>
<p>Instead of returning a "lowest common denominator" C# <code>object</code> type which could contain either result, we can use a discriminated union or "choice" type to represent the output with strong(er) typing. Here's a minimal implementation of an "A or B" <code>Choice</code> type:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Choice</span>&lt;<span class="hljs-title">A</span>, <span class="hljs-title">B</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Choice</span>(<span class="hljs-params">A <span class="hljs-keyword">value</span></span>)</span> { Value = <span class="hljs-keyword">value</span>; }
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Choice</span>(<span class="hljs-params">B <span class="hljs-keyword">value</span></span>)</span> { Value = <span class="hljs-keyword">value</span>; }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">dynamic</span> Value { <span class="hljs-keyword">get</span>; }

    <span class="hljs-comment">// equality and string representation delegated to member (Value)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">Equals</span>(<span class="hljs-params"><span class="hljs-keyword">object</span>? obj</span>)</span> =&gt; Value.Equals(obj);
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">int</span> <span class="hljs-title">GetHashCode</span>(<span class="hljs-params"></span>)</span> =&gt; Value.GetHashCode();
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">string</span> <span class="hljs-title">ToString</span>(<span class="hljs-params"></span>)</span> =&gt; Value.ToString();
}
</code></pre>
<p>I say "stronger" typing because the <code>Value</code> parameter is still dynamically typed. (True Discriminiated Unions, a core feature of F#, <a target="_blank" href="https://github.com/dotnet/csharplang/issues/113">appear to be coming in C#</a> , which is exciting.) With this in place, the <code>Result</code> type becomes:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Result implementation</span>
<span class="hljs-keyword">class</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TOut</span>&gt; : <span class="hljs-title">Choice</span>&lt;<span class="hljs-title">TOut</span>, <span class="hljs-title">Error</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TOut</span>&gt;(<span class="hljs-params">TOut <span class="hljs-keyword">value</span></span>)</span> =&gt; <span class="hljs-keyword">new</span> Result&lt;TOut&gt;(<span class="hljs-keyword">value</span>);
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TOut</span>&gt;(<span class="hljs-params">Error error</span>)</span> =&gt; <span class="hljs-keyword">new</span> Result&lt;TOut&gt;(error);

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Result</span>(<span class="hljs-params">TOut <span class="hljs-keyword">value</span></span>) : <span class="hljs-title">base</span>(<span class="hljs-params"><span class="hljs-keyword">value</span></span>)</span> { }
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Result</span>(<span class="hljs-params">Error error</span>) : <span class="hljs-title">base</span>(<span class="hljs-params">error</span>)</span> { }
}
</code></pre>
<p>As a convenience, I added implicit conversions from either of the two individual output types (<code>TOut</code> and <code>Error</code>, here), into the <code>Result&lt;TOut&gt;</code> type.</p>
<p>Using this structure, the signature of the error-generating switch function becomes:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// switch function signature in Func-form</span>
Func&lt;TIn, Result&lt;TOut&gt;&gt; switchFunction; 

<span class="hljs-comment">// ...and an example method</span>
<span class="hljs-function">Result&lt;<span class="hljs-keyword">float</span>&gt; <span class="hljs-title">MaybeInverse</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> val</span>)</span> =&gt; val <span class="hljs-keyword">switch</span>
{
    <span class="hljs-number">0f</span> =&gt; <span class="hljs-keyword">new</span> Error(<span class="hljs-string">"Inverse!"</span>),
    _  =&gt; (Result&lt;<span class="hljs-keyword">float</span>&gt;)(<span class="hljs-number">1</span> / val)
};
</code></pre>
<h2 id="heading-bind">Bind</h2>
<p>Bind is an "adapter block" that glues these error-generating functions together into a two-track model, so that the happy paths compose, and the failure path short-circuits by forwarding any previous Errors.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Bind signature in Func-form</span>
Func&lt;Func&lt;TIn, Result&lt;TOut&gt;&gt;, Func&lt;Result&lt;TIn&gt;, Result&lt;TOut&gt;&gt; bind;

<span class="hljs-comment">// ...and implemented as a method</span>
Func&lt;Result&lt;TIn&gt;, Result&lt;TOut&gt;&gt; Bind&lt;TIn, TOut&gt;(Func&lt;TIn, Result&lt;TOut&gt;&gt; switchFunction)
{
    <span class="hljs-keyword">return</span> choice =&gt; choice.Value <span class="hljs-keyword">switch</span>
    {
        Error e =&gt; (Result&lt;TOut&gt;)e,
        TIn tIn =&gt; switchFunction(tIn).Value <span class="hljs-keyword">switch</span>
        {
            Error eOut =&gt; eOut,
            TOut tOut  =&gt; tOut
        }
    };
}
</code></pre>
<h2 id="heading-map">Map</h2>
<p>A map is an adapter block that turns (or "lifts") a 1-track function into a two-track function<sup>2</sup>:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Map signature in Func-form</span>
Func&lt;Func&lt;TIn, TOut&gt;, Func&lt;Result&lt;TIn&gt;, Result&lt;TOut&gt;&gt; map;

<span class="hljs-comment">// ...and implemented as a method (in terms of bind)</span>
Func&lt;Result&lt;TIn&gt;, Result&lt;TOut&gt;&gt; Map&lt;TIn, TOut&gt;(Func&lt;TIn, TOut&gt; oneTrackFunction)
{
    <span class="hljs-keyword">return</span> Bind&lt;TIn, TOut&gt;(input =&gt; oneTrackFunction(input));
}
</code></pre>
<h2 id="heading-tee">Tee</h2>
<p>Tee is an adapter block that turns a "dead-end" function (or Action) into a one-track function:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Tee signature in Func-form</span>
Func&lt;Action&lt;TIn&gt;, Func&lt;TIn, TIn&gt;&gt; tee;

<span class="hljs-comment">// ...and implemented as a method</span>
<span class="hljs-function"><span class="hljs-title">Func</span>&lt;<span class="hljs-title">TIn</span>, <span class="hljs-title">TIn</span>&gt; <span class="hljs-title">Tee</span>&lt;<span class="hljs-title">TIn</span>&gt;(<span class="hljs-params">Action&lt;TIn&gt; deadEndFunction</span>)</span>
{
    <span class="hljs-keyword">return</span> (TIn input) =&gt; { deadEndFunction(input); <span class="hljs-keyword">return</span> input; };
}
</code></pre>
<p>These are "side-effect" functions that might write to the database, send an email, log an action, print to screen, etc. Now that we have a one-track function, we can use Map to turn it into a two track function. Let's call that composite capability "Audit":</p>
<h2 id="heading-audit">Audit</h2>
<pre><code class="lang-csharp"><span class="hljs-comment">// Audit signature in Func-form</span>
Func&lt;Action&lt;TIn&gt;, Func&lt;Result&lt;TIn&gt;, Result&lt;TIn&gt;&gt;&gt; audit;

<span class="hljs-comment">// ...and implemented as a method (in terms of Map and Tee)</span>
Func&lt;Result&lt;TIn&gt;, Result&lt;TIn&gt;&gt; Audit&lt;TIn&gt;(Action&lt;TIn&gt; deadEndFunction)
{
    <span class="hljs-keyword">return</span> Map(Tee(deadEndFunction));
}

<span class="hljs-comment">// ...or alternatively, directly in terms of Bind</span>
Func&lt;Result&lt;TIn&gt;, Result&lt;TIn&gt;&gt; Audit&lt;TIn&gt;(Action&lt;TIn&gt; deadEndFunction)
{
    <span class="hljs-keyword">return</span> Bind&lt;TIn, TIn&gt;(input =&gt; { deadEndFunction(input); <span class="hljs-keyword">return</span> input; });
}
</code></pre>
<p>A supervisory or "audit" function is transformed to a two-track function by just passing Success and Failure inputs straight through to the outputs, and performing Actions on them along the way as needed.</p>
<p>lightw8 comment: You could imagine an enhancement to the Tee method to return a Failure (instead of void), if the action fails. But, then it's "just" an error-generating function (a good thing!), and we already have Bind for that. Speaking of which...</p>
<h2 id="heading-exceptions">Exceptions</h2>
<p>In this model the guidance is to "catch and don't release" exceptions that are relevant to this level of the computation, and return them instead as a formal Failure result. It's a judgement call about which exceptions to let propagate up the stack, but we should handle things that are reasonably in-scope. We don't want the code blowing up for things we have context for fixing/handling, but we also don't want to guess at the intent of the caller.</p>
<h2 id="heading-validation-events-undo-retries">Validation, Events, Undo, Retries</h2>
<p>Around minute 37 of the talk, Mr. Wlaschin talks about parallel validation logic, and this leads to a general discussion on how we can augment the <code>Result</code> structure to contain additional information on the <em>success</em> path, such as store/forward event data and allow for rollback/undo types of actions. Similarly, enhancing the error path can be used to enable retries in the case of errors. I won't elaborate further for now, but this might be interesting to revisit sometime in more detail.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>I enjoyed the challenge of creating these implementations without checking outside sources. So caveat emptor - no guarantees your trains will arrive on time with this code! In the next post, I'll work through a few examples of how we can use these functional building blocks in practice.</p>
<p>Thanks for reading, and talk soon. -David</p>
<p><sup>1 </sup> Concept and header image credit to Scott Wlaschin <a target="_blank" href="https://fsharpforfunandprofit.com/rop/">here</a></p>
<p><sup>2</sup> C# type inference looks like it will be <a target="_blank" href="https://kenbonny.net/introducing-csharp-10">improving in C# 10</a>, but for now, we need to explicitly specify the generic types when calling <code>Bind&lt;TIn, TOut&gt;(...)</code></p>
]]></content:encoded></item><item><title><![CDATA[Railway Oriented Programming]]></title><description><![CDATA[I enjoy solving problems big and small, whether at my day job as a Biomedical Engineer, or on the weekend when I'm learning router port-forwarding and Raspberry Pi computing so my niece and nephews can check in on the latest backyard activity of our ...]]></description><link>https://lightw8.blog/railway-oriented-programming</link><guid isPermaLink="true">https://lightw8.blog/railway-oriented-programming</guid><category><![CDATA[Functional Programming]]></category><category><![CDATA[C#]]></category><category><![CDATA[error handling]]></category><dc:creator><![CDATA[David Cuccia]]></dc:creator><pubDate>Tue, 08 Jun 2021 16:03:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1622927264633/hEpr2me31.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I enjoy solving problems big and small, whether at my <a target="_blank" href="https://modulim.com/">day job</a> as a Biomedical Engineer, or on the weekend when I'm learning router port-forwarding and Raspberry Pi computing so my niece and nephews can check in on the latest backyard activity of our newly-webcam-monitored squirrel picnic bench.</p>
<p>Sometimes a problem requires a simple "hack," or 1-off solution specific to the issue at hand. Sometimes, though, there's a characteristic of a solution that keeps recurring. Recognizing those patterns can be powerful, because they can be saved and re-applied to new problems in the future, or in a new domain. Practice noticing the "shape" of a problem-solution pair, then over time you accumulate these patterns in your toolbox for future use.</p>
<p>I just finished re-watching one of my <a target="_blank" href="https://vimeo.com/113707214">favorite talks</a> by Scott Wlaschin on a topic he coins "Railway Oriented Programming." It's a concise, funny and jargon-light view of patterns in functional programming (that's a lower-case "functional" by the way - while he uses some F# code, the patterns he describes are universal to all languages). He talks specifically about "Straying from the happy path," and how to deal with things that inevitably go wrong with software, without turning your code into a total mess. Per his web page <a target="_blank" href="https://fsharpforfunandprofit.com/rop">fsharpforfunandprofit.com/rop</a>, here's the blurb for the talk:</p>
<blockquote>
<p>Many examples in functional programming assume that you are always on the “happy path”. But to create a robust real world application you must deal with validation, logging, network and service errors, and other annoyances.</p>
<p>So, how do you handle all this in a clean functional way?</p>
<p>This talk will provide a brief introduction to this topic, using a fun and easy-to-understand railway analogy.</p>
</blockquote>
<p>In the presentation, Mr. Wlaschin shows how originally-simple, linear, "green field" code gets turned inside out as we attempt to make it robust and "enterprise grade." Our ability to quickly understand and reason about the code plummets as different structures, branches and orchestrations are put in place to combat potential errors. The result is complex code that is very difficult to understand in terms of it's functional intent, and therefore difficult to maintain/modify/improve. This then quickly becomes "read-only" code that accumulates technical debt, and things ultimately grind to a halt. Traditional object-oriented approaches to wrangle some of this complexity - interfaces, proxies, factories, containers - tend to lead to yet more abstraction and still make things initially difficult to understand. It can be a lot of concepts to keep in your head all at once, and can often distract from communicating the intent of the code.</p>
<p>What's remarkable is how in the subsequent presentation he demonstrates that so many of these different challenges in modern software engineering - defensive programming, error checking, exception handling, coordinating async continuations, middleware audits, timeouts/retries - can be mapped to, addressed, and simplified using this simple railway mental model and a few functional-style helpers. The linear code that gets turned into a "Pyramid of Doom" of nested conditionals for catching potential errors, becomes re-linearized by putting the error handling pattern in the functional operators themselves.</p>
<p>The strategy is basically to adapt existing happy-path functions into two-output "tracks": the "happy path" for the code behaving correctly, and an "error path" for errors, exceptions, etc. Here's a quick example (<code>Choice&lt;T1, T2&gt;</code> is a "Discriminated Union" type...more on that later):</p>
<pre><code class="lang-csharp"><span class="hljs-function">Choice&lt;<span class="hljs-keyword">float</span>, Error&gt; <span class="hljs-title">MaybeInverse</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> val</span>)</span> =&gt; val <span class="hljs-keyword">switch</span>
{
    <span class="hljs-number">0f</span> =&gt; <span class="hljs-keyword">new</span> Error(<span class="hljs-string">"Inverse!"</span>),
    _  =&gt; (<span class="hljs-number">1</span> / val)
};
</code></pre>
<p>Then, using some simple helper building blocks (shh - they're monads, but I won't tell if you don't), you can stitch these individual components together into a pipeline, which will short circuit or accumulate the undesirable outcomes. He also shows the benefits of mapping errors and exceptions into meaningful domain-model entities that can be carried along with the normal data flow. These help the consumer of the code make more meaningful, reasoned use of the information, versus them having to deal with the multiple "early exit" possibilities that can explode in complexity as the application grows. At the end, he shows the original top-level naïve code next to the new robust error handling code: they're identical. The complexity is managed by the "glue" helper functions underneath, and the meaning/intent of the original code is preserved.</p>
<p>A great benefit of learning Mr. Wlaschin's railway track model is that along the way we also get one of the most accessible and understandable descriptions of functional programming constructs like monads and monoids. It was so clear that without much help, I was able to "follow along at home" and create some C# representations of the concepts and helper functions he describes. In the next post, I'll share some of that work.</p>
<p>Thanks for reading, and talk soon. David</p>
<p>P.S. As an aside, I'm starting this blog because I enjoyed this recent <a target="_blank" href="https://hanselminutes.com/784/doing-open-source-with-brian-douglas">Hanselminutes podcast</a> with <a target="_blank" href="https://twitter.com/bdougieYO">Brian Douglas</a>, in which Mr. Douglas mentions platforms like <a target="_blank" href="https://dev.to/">dev.to</a> and <a target="_blank" href="https://hashnode.com/">hashnode</a>, and encourages devs to avoid common blogging pitfalls and just get to the creating part. Thanks to him for the nudge.</p>
]]></content:encoded></item></channel></rss>