ploeh blog https://blog.ploeh.dk danish software design en-us Mark Seemann Mon, 18 Aug 2025 05:45:15 UTC Mon, 18 Aug 2025 05:45:15 UTC Song recommendations with Haskell free monads https://blog.ploeh.dk/2025/08/18/song-recommendations-with-haskell-free-monads/ Mon, 18 Aug 2025 05:44:00 UTC <div id="post"> <p> <em>A surprisingly easy refactoring.</em> </p> <p> This article is part of a larger series titled <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">Alternative ways to design with functional programming</a>. In short, it uses <a href="https://www.haskell.org/">Haskell</a>, <a href="https://fsharp.org/">F#</a>, and C# to present various internal architectures to deal with an example problem. Please refer to the table of contents included with the <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">first article</a> to get a sense of what has already been covered. </p> <p> In this article, you'll see <a href="/2025/08/11/song-recommendations-with-free-monads">how a free monad may be used to address the problem</a> that when data size is sufficiently large, you may need to load it piecemeal, based on the results of previous steps in an algorithm. In short, the problem being addressed is to calculate a list of song recommendations based on so-called 'scrobble' data from a multitude of other users' music playback history. </p> <p> If you want to follow along with the Git repository, the code presented here is from the Haskell repository's <em>free</em> branch. </p> <h3 id="efc53e7ce8b747ddb18acd0958527b06"> Starting point <a href="#efc53e7ce8b747ddb18acd0958527b06">#</a> </h3> <p> Instead of starting from scratch from the code base presented in <a href="/2025/04/21/porting-song-recommendations-to-haskell">Porting song recommendations to Haskell</a>, I'll start at what was an interim refactoring step in <a href="/2025/06/30/song-recommendations-from-haskell-combinators">Song recommendations from Haskell combinators</a>: </p> <p> <pre><span style="color:#2b91af;">getRecommendations</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">SongService</span>&nbsp;a&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;a&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">String</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">IO</span>&nbsp;[<span style="color:blue;">Song</span>] getRecommendations&nbsp;srvc&nbsp;un&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations </span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;Impure </span>&nbsp;&nbsp;scrobbles&nbsp;&lt;-&nbsp;getTopScrobbles&nbsp;srvc&nbsp;un &nbsp;&nbsp;<span style="color:green;">--&nbsp;Pure </span>&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;scrobblesSnapshot&nbsp;=&nbsp;<span style="color:blue;">take</span>&nbsp;100&nbsp;$&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;scrobbleCount)&nbsp;scrobbles &nbsp;&nbsp;<span style="color:green;">--&nbsp;Impure </span>&nbsp;&nbsp;recommendations&nbsp;&lt;- &nbsp;&nbsp;&nbsp;&nbsp;join&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\scrobble&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;join&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\otherListener&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;scrobbledSong&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;10&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating&nbsp;.&nbsp;scrobbledSong)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;(songHasVerifiedArtist&nbsp;.&nbsp;scrobbledSong)&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopScrobbles&nbsp;srvc&nbsp;(userName&nbsp;otherListener))&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;20&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;userScrobbleCount)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;((10_000&nbsp;&lt;=)&nbsp;.&nbsp;userScrobbleCount)&nbsp;=&lt;&lt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopListeners&nbsp;srvc&nbsp;(songId&nbsp;$&nbsp;scrobbledSong&nbsp;scrobble)) &nbsp;&nbsp;&nbsp;&nbsp;scrobblesSnapshot &nbsp;&nbsp;<span style="color:green;">--&nbsp;Pure </span>&nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;<span style="color:blue;">take</span>&nbsp;200&nbsp;$&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating)&nbsp;recommendations</pre> </p> <p> The reason I wanted to start here is that with the <code>IORef</code> out of the way, the only <code>IO</code>-bound code remaining involves the <code>SongService</code> methods. </p> <p> The plan is to replace the <code>SongService</code> type class with a free monad. Once that's done, there's no more <code>IO</code> left; it will have been replaced by the free monad. That's why it's easiest to first get rid of everything else involving <code>IO</code>. If I didn't, the refactoring wouldn't be as easy. As Kent Beck wrote, </p> <blockquote> <p> "for each desired change, make the change easy (warning: this may be hard), then make the easy change" </p> <footer><cite><a href="https://x.com/kentbeck/status/250733358307500032">Tweet</a>, Kent Beck, 2012</cite></footer> </blockquote> <p> Starting from the above incarnation of the code makes the change easy. </p> <h3 id="1d5af97b6c2542698aa20cb449cc771e"> Functor <a href="#1d5af97b6c2542698aa20cb449cc771e">#</a> </h3> <p> As a reminder, this type class is the one I'm getting rid of: </p> <p> <pre><span style="color:blue;">class</span>&nbsp;<span style="color:#2b91af;">SongService</span>&nbsp;a&nbsp;<span style="color:blue;">where</span> &nbsp;&nbsp;<span style="color:#2b91af;">getTopListeners</span>&nbsp;<span style="color:blue;">::</span>&nbsp;a&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Int</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">IO</span>&nbsp;[<span style="color:blue;">User</span>] &nbsp;&nbsp;<span style="color:#2b91af;">getTopScrobbles</span>&nbsp;<span style="color:blue;">::</span>&nbsp;a&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">String</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">IO</span>&nbsp;[<span style="color:blue;">Scrobble</span>]</pre> </p> <p> Converting such an 'interface' to a <a href="https://en.wikipedia.org/wiki/Tagged_union">sum type</a> of instructions is such a well-defined process that I think it could be automated (<a href="https://xkcd.com/1205/">if it were worth the effort</a>). You take each method of the type class and make it a case in the sum type. </p> <p> <pre><span style="color:blue;">data</span>&nbsp;SongInstruction&nbsp;a&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;GetTopListeners&nbsp;Int&nbsp;([User]&nbsp;-&gt;&nbsp;a) &nbsp;&nbsp;|&nbsp;GetTopScrobbles&nbsp;String&nbsp;([Scrobble]&nbsp;-&gt;&nbsp;a) &nbsp;&nbsp;<span style="color:blue;">deriving</span>&nbsp;(<span style="color:#2b91af;">Functor</span>)</pre> </p> <p> I usually think of such a type as representing possible instructions in a little domain-specific language (DSL). In this case, the DSL allows only two instructions: <code>GetTopListeners</code> and <code>GetTopScrobbles</code>. </p> <p> If you've never seen a free monad before, you may be wondering: <em>What's the <code>a</code>?</em> It's what we may call the <em>carrier type</em>. If you haven't worked with, say, <a href="https://bartoszmilewski.com/2017/02/28/f-algebras/">F-Algebras</a>, this takes some time getting used to, but the carrier type represents a 'potential'. As presented here, it may be anything, but when you want to evaluate or 'run' the program, the <code>a</code> turns out to be the return type of the evaluation. </p> <p> Since <code>SongInstruction</code> is a <code>Functor</code>, it's possible to map <code>a</code> to <code>b</code>, or one concrete type to another, so that the <code>a</code> type parameter can take on more than one concrete type during a small 'program' written in the DSL. </p> <p> In any case, at this point, <code>SongInstruction a</code> is only a <code>Functor</code>, and not yet a <code>Monad</code>. </p> <h3 id="44bb4cf9252b4eeb8ae0fe302a4cbd42"> Free monad <a href="#44bb4cf9252b4eeb8ae0fe302a4cbd42">#</a> </h3> <p> You may, if you want to, introduce a type alias that makes <code>SongInstruction a</code> (or, rather, its <code>Free</code> wrapper) a <code>Monad</code>. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;SongProgram&nbsp;=&nbsp;Free&nbsp;SongInstruction</pre> </p> <p> The <code>Free</code> wrapper comes from <a href="https://hackage.haskell.org/package/free/docs/Control-Monad-Free.html">Control.Monad.Free</a>. </p> <p> In the rest of the code listings in this article, I make no use of this type alias, so I only present it here because it's the type <a href="https://en.wikipedia.org/wiki/Glasgow_Haskell_Compiler">the compiler</a> will infer when running the test. In other words, while never explicitly stated, this is going to be the de-facto free monad in this article. </p> <h3 id="368a9eb18f5846d49896baacf66fbb25"> From action to function <a href="#368a9eb18f5846d49896baacf66fbb25">#</a> </h3> <p> Changing the <code>getRecommendations</code> action to a function that returns (effectively) <code>Free (SongInstruction [Song])</code> turned out to be easier than I had expected. </p> <p> Since I've <a href="/2024/11/04/pendulum-swing-no-haskell-type-annotation-by-default">decided to omit type declarations whenever possible</a>, refactoring is easier because the type inferencing system can allow changes to function and action types to ripple through to the ultimate callers. (To be clear, however, I here show some of the code with type annotations. I've only added those in the process of writing this article, as an aid to you, the reader. You will not find those type annotations in the Git repository.) </p> <p> First, I added a few helper methods to create 'program instructions': </p> <p> <pre>getTopListeners&nbsp;sid&nbsp;=&nbsp;liftF&nbsp;$&nbsp;GetTopListeners&nbsp;sid&nbsp;<span style="color:blue;">id</span> getTopScrobbles&nbsp;un&nbsp;=&nbsp;liftF&nbsp;$&nbsp;GetTopScrobbles&nbsp;un&nbsp;<span style="color:blue;">id</span></pre> </p> <p> The <code>liftF</code> function wraps a <code>Functor</code> (here, <code>SongInstruction</code>) in a free monad. This makes it easer to write programs in that DSL. </p> <p> The only changes now required to <code>getRecommendations</code> is to remove <code>srvc</code> in four places: </p> <p> <pre><span style="color:#2b91af;">getRecommendations</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">MonadFree</span>&nbsp;<span style="color:blue;">SongInstruction</span>&nbsp;m&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:#2b91af;">String</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;m&nbsp;[<span style="color:blue;">Song</span>] getRecommendations&nbsp;un&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations </span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;Impure </span>&nbsp;&nbsp;scrobbles&nbsp;&lt;-&nbsp;getTopScrobbles&nbsp;un &nbsp;&nbsp;<span style="color:green;">--&nbsp;Pure </span>&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;scrobblesSnapshot&nbsp;=&nbsp;<span style="color:blue;">take</span>&nbsp;100&nbsp;$&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;scrobbleCount)&nbsp;scrobbles &nbsp;&nbsp;<span style="color:green;">--&nbsp;Impure </span>&nbsp;&nbsp;recommendations&nbsp;&lt;- &nbsp;&nbsp;&nbsp;&nbsp;join&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\scrobble&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;join&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\otherListener&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;scrobbledSong&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;10&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating&nbsp;.&nbsp;scrobbledSong)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;(songHasVerifiedArtist&nbsp;.&nbsp;scrobbledSong)&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopScrobbles&nbsp;(userName&nbsp;otherListener))&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;20&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;userScrobbleCount)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;((10_000&nbsp;&lt;=)&nbsp;.&nbsp;userScrobbleCount)&nbsp;=&lt;&lt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopListeners&nbsp;(songId&nbsp;$&nbsp;scrobbledSong&nbsp;scrobble)) &nbsp;&nbsp;&nbsp;&nbsp;scrobblesSnapshot &nbsp;&nbsp;<span style="color:green;">--&nbsp;Pure </span>&nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;<span style="color:blue;">take</span>&nbsp;200&nbsp;$&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating)&nbsp;recommendations</pre> </p> <p> It's almost the same code as before. The only difference is that <code>srvc</code> is not longer a parameter to either <code>getRecommendations</code>, <code>getTopListeners</code>, or <code>getTopScrobbles</code>. </p> <p> Again, I'll point out that I've only added the type annotation for the benefit of you, the reader. We see now, however, that the return type is <code>m [Song]</code>, where <code>m</code> is any <code>MonadFree SongInstruction</code>. </p> <h3 id="9ebdd39dee9a4b3f949de971fb2b3801"> Interpreter <a href="#9ebdd39dee9a4b3f949de971fb2b3801">#</a> </h3> <p> As described in <a href="/2025/04/21/porting-song-recommendations-to-haskell">Porting song recommendations to Haskell</a> I use a <code>FakeSongService</code> as a <a href="https://martinfowler.com/bliki/TestDouble.html">Test Double</a>. <code>SongService</code> is now gone, but I can repurpose the instance implementation as an interpreter of <code>SongInstruction</code> programs. </p> <p> <pre><span style="color:#2b91af;">interpret</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">FakeSongService</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">Free</span>&nbsp;<span style="color:blue;">SongInstruction</span>&nbsp;a&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;a interpret&nbsp;(FakeSongService&nbsp;ss&nbsp;us)&nbsp;=&nbsp;iter&nbsp;eval &nbsp;&nbsp;<span style="color:blue;">where</span> &nbsp;&nbsp;&nbsp;&nbsp;eval&nbsp;(GetTopListeners&nbsp;sid&nbsp;next)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">uncurry</span>&nbsp;User&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Map.toList&nbsp;(<span style="color:blue;">sum</span>&nbsp;&lt;$&gt;&nbsp;Map.<span style="color:blue;">filter</span>&nbsp;(Map.member&nbsp;sid)&nbsp;us) &nbsp;&nbsp;&nbsp;&nbsp;eval&nbsp;(GetTopScrobbles&nbsp;un&nbsp;next)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;(\(sid,&nbsp;c)&nbsp;-&gt;&nbsp;Scrobble&nbsp;(ss&nbsp;!&nbsp;sid)&nbsp;c)&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Map.toList&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Map.findWithDefault&nbsp;Map.empty&nbsp;un&nbsp;us</pre> </p> <p> Compare that code to the previous <code>SongService</code> instance shown in <a href="/2025/04/21/porting-song-recommendations-to-haskell">Porting song recommendations to Haskell</a>. The functions are nearly identical, only <code>return</code> is replaced by <code>next</code>, and a few other, minute changes. </p> <h3 id="f46a84de631943c0a1d3f59d9fcfc5e8"> Test changes <a href="#f46a84de631943c0a1d3f59d9fcfc5e8">#</a> </h3> <p> The hardest part of this refactoring was to adjust the tests. As I've described in <a href="/2021/06/14/new-book-code-that-fits-in-your-head">Code That Fits in Your Head</a>, I don't like when I have to change the System Under Test and the test code in the same commit, but in this case I lacked the skill to do it more incrementally. </p> <p> The issue is that since <code>SongService</code> methods were <code>IO</code>-bound, the tests ran in <code>IO</code>. Particularly for the <a href="https://hackage.haskell.org/package/QuickCheck">QuickCheck</a> properties, I had to remove the <code>ioProperty</code> and <code>monadicIO</code> combinators. This also meant adjusting some of the assertions. The <code>"One user, some songs"</code> test now looks like this: </p> <p> <pre>testProperty&nbsp;<span style="color:#a31515;">&quot;One&nbsp;user,&nbsp;some&nbsp;songs&quot;</span>&nbsp;$&nbsp;\ &nbsp;&nbsp;(ValidUserName&nbsp;user) &nbsp;&nbsp;(<span style="color:blue;">fmap</span>&nbsp;getSong&nbsp;-&gt;&nbsp;songs) &nbsp;&nbsp;-&gt;&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;scrobbleCounts&nbsp;&lt;-&nbsp;vectorOf&nbsp;(<span style="color:blue;">length</span>&nbsp;songs)&nbsp;$&nbsp;choose&nbsp;(1,&nbsp;100) &nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;scrobbles&nbsp;=&nbsp;<span style="color:blue;">zip</span>&nbsp;songs&nbsp;scrobbleCounts &nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;srvc&nbsp;=&nbsp;<span style="color:blue;">foldr</span>&nbsp;(<span style="color:blue;">uncurry</span>&nbsp;(scrobble&nbsp;user))&nbsp;emptyService&nbsp;scrobbles &nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;actual&nbsp;=&nbsp;interpret&nbsp;srvc&nbsp;$&nbsp;getRecommendations&nbsp;user &nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;counterexample&nbsp;<span style="color:#a31515;">&quot;Should&nbsp;be&nbsp;empty&quot;</span>&nbsp;(<span style="color:blue;">null</span>&nbsp;actual)</pre> </p> <p> Notice the interpreter in action, to the left of the application of <code>getRecommendations</code>. Since <code>interpret</code> reduces any <code>Free SongInstruction a</code> to <code>a</code>, it evaluates <code>Free SongInstruction [Song]</code> to <code>[Song]</code>; that's the type of <code>actual</code>. </p> <p> The change represents a net benefit in my view. All tests are now pure, instead of running in <code>IO</code>. This makes them simpler. </p> <h3 id="8e30c90a008d4a0088ab79a4a6d15532"> Final refactoring <a href="#8e30c90a008d4a0088ab79a4a6d15532">#</a> </h3> <p> The above version of the code was only a good starting point for making the changeover to a free monad. Extracting the usual helper functions, you can arrive at this variation of the <code>getRecommendations</code> function: </p> <p> <pre>getRecommendations&nbsp;un&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations </span> &nbsp;&nbsp;userTops&nbsp;&lt;-&nbsp;getTopScrobbles&nbsp;un&nbsp;&lt;&amp;&gt;&nbsp;getUsersOwnTopScrobbles &nbsp;&nbsp;otherListeners&nbsp;&lt;-&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(getTopListeners&nbsp;.&nbsp;(songId&nbsp;.&nbsp;scrobbledSong))&nbsp;userTops&nbsp;&lt;&amp;&gt; &nbsp;&nbsp;&nbsp;&nbsp;getOtherUsersWhoListenedToTheSameSongs&nbsp;.&nbsp;join &nbsp;&nbsp;songs&nbsp;&lt;- &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(getTopScrobbles&nbsp;.&nbsp;userName)&nbsp;otherListeners&nbsp;&lt;&amp;&gt; &nbsp;&nbsp;&nbsp;&nbsp;getTopScrobblesOfOtherUsers&nbsp;.&nbsp;join &nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;aggregateTheSongsIntoRecommendations&nbsp;songs</pre> </p> <p> Again, it looks a lot like one of the variations shown in <a href="/2025/04/21/porting-song-recommendations-to-haskell">Porting song recommendations to Haskell</a>, just without the <code>srvc</code> parameter. </p> <h3 id="e7f18ceb9a9d45a19bfea1f248ee872e"> Conclusion <a href="#e7f18ceb9a9d45a19bfea1f248ee872e">#</a> </h3> <p> Refactoring from a type class, which in other languages could be an interface, is so easy that you may rightfully ask what the point is. To me, the most important benefit is that you restrict your options. As I discussed in a podcast episode some years ago, <a href="https://www.dotnetrocks.com/details/1542">constraints liberate</a>. Here, we move from allowing <em>anything</em> (<code>IO</code>) to only allowing a limited set of instructions. </p> <p> You could argue that a 'real' interpreter (rather than a test-specific interpreter) might run in <code>IO</code>, such as the <a href="/2017/06/28/pure-times-in-haskell">interpreter shown here</a>. Still, I would argue that most interpreters are smaller, and more stable, than the programs you may write with free monads. Thus, the amount of code you have to review that's allowed to do anything is smaller than it otherwise would have been. </p> <p> In any case, refactoring to a free monad wasn't too difficult in Haskell. How easy is it in F#? </p> <p> <strong>Next:</strong> Song recommendations with F# free monads. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/08/18/song-recommendations-with-haskell-free-monads Song recommendations with free monads https://blog.ploeh.dk/2025/08/11/song-recommendations-with-free-monads/ Mon, 11 Aug 2025 06:04:00 UTC <div id="post"> <p> <em>A Golden Hammer.</em> </p> <p> This article is part of a larger article series about <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">alternative ways to design with functional programming</a>, particularly when faced with massive data loads. In previous articles in the series, you've seen various alternatives that may or may not enable you to solve the example problem using functional programming. Each of the previous alternatives, <a href="/2025/04/28/song-recommendations-as-an-impureim-sandwich">applying the Recawr Sandwich pattern</a>, <a href="/2025/06/09/song-recommendations-from-combinators">employing functional combinators</a>, or <a href="/2025/07/14/song-recommendations-with-pipes-and-filters">using pipes and filters</a>, came with some trade-offs. Either there were limits to the practicality of the architecture, or it wasn't really functional after all. </p> <p> In this, and the following three articles, we finally reach for a universal solution: Free monads. </p> <p> By <em>universal</em> I mean: It's always possible to do this (if your language supports it). It doesn't follow that it's always a good idea. </p> <p> So there are trade-offs here, too. </p> <p> As is usually the case, although sometimes I forget to write it, this and the following articles are <em>descriptive</em>, not prescriptive. In no way do I insist that things must be done this way. I only present examples as options. </p> <h3 id="137ca70378114954a7905e93ba8c21a8"> The last resort <a href="#137ca70378114954a7905e93ba8c21a8">#</a> </h3> <p> If using a free monad is a universal possibility, then why is that not the default option? Why have I waited this long in the article series before covering it? </p> <p> For more than a single reason. </p> <p> It's an 'advanced' technique, and I predict that to many programmers, it looks like black magic. If you're working with a team unready for free monads, foisting it upon them are unlikely to lead to anything good. </p> <p> The following story may illustrate the problem. It's not about free monads, but rather about Dependency Injection (DI) Containers. A specific one, actually. I was consulting with a team, working as a temporary lead developer for about half a year. I made a number of technical decisions, some of them good and others not so much. Among the latter was the decision to use the <a href="https://www.castleproject.org/projects/windsor/">Castle Windsor</a> DI Container. </p> <p> Not that Castle Windsor was a bad library. After having done the research and written <a href="/ref/diidn">a book</a>, I considered it the best DI Container available. </p> <p> The problem, rather, was that the rest of the team considered it 'automagical'. Not that they weren't sophisticated programmers, but they had no interest learning the Castle Windsor API. They didn't consider it part of their core work, and they didn't think it provided enough benefit compared to the maintenance burden it imposed. </p> <p> They humoured me as long as I stayed on, but also explicitly told me that they'd rip it out as soon as I was gone. In the meantime, I became the Castle Windsor bottleneck. Everything related to that piece of technology had to go through me, since I was the only person who understood how it worked. </p> <p> A few years later, I returned to that team and that code base on a new project, and they'd kept their word. They'd removed Castle Windsor in favour of <a href="/2014/06/10/pure-di">Pure DI</a>, bless them. </p> <p> This experience was crucial in making me realize that, despite their intellectual attraction, DI Containers are rarely the correct choice. </p> <p> Free monads look to me as though they belong to the same category. While I don't have a similar story to tell, I'm wary of recommending them unless other options are played out. I'll refer you to the decision flowchart in the article <a href="/2017/08/07/f-free-monad-recipe">F# free monad recipe</a>. </p> <h3 id="3c98545a69c042f19a304ac36db1392f"> Language support <a href="#3c98545a69c042f19a304ac36db1392f">#</a> </h3> <p> Another concern about free monads is whether your language of choice supports them. They fit perfectly in <a href="https://www.haskell.org/">Haskell</a>, which is also the reason I start this sub-series of articles with a Haskell example. </p> <ul> <li><a href="/2025/08/18/song-recommendations-with-haskell-free-monads">Song recommendations with Haskell free monads</a></li> <li>Song recommendations with F# free monads</li> <li>Song recommendations with C# free monads</li> </ul> <p> In F# you'll have to do some more legwork, but once you've added the required infrastructure, the 'user code' based on free monads is perfectly fine. </p> <p> C#, on the other hand, doesn't have all the language features that will make free monads a good experience. I wouldn't suggest using a free monad in C#. I include the article with the C# free monad for demonstration purposes only. </p> <p> These articles will not introduce free monads to novice readers. For a gentler introduction, see the article series <a href="/2017/07/10/pure-interactions">Pure interactions</a>. </p> <h3 id="2d17efd5d83346ddb73c33d991da8096"> Conclusion <a href="#2d17efd5d83346ddb73c33d991da8096">#</a> </h3> <p> When all else fails, and you just <em>must</em> write <a href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a>, reach for free monads. They are not for everyone, or every language, but they do offer a functional solution to problems with large data sets or rich interaction with users or the environment. </p> <p> <strong>Next:</strong> <a href="/2025/08/18/song-recommendations-with-haskell-free-monads">Song recommendations with Haskell free monads</a>. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/08/11/song-recommendations-with-free-monads Testing races with a synchronizing Decorator https://blog.ploeh.dk/2025/08/04/testing-races-with-a-synchronizing-decorator/ Mon, 04 Aug 2025 07:24:00 UTC <div id="post"> <p> <em>Synchronized database reads for testing purposes.</em> </p> <p> In a previous article, you saw how to use a <a href="/2025/06/02/testing-races-with-a-slow-decorator">slow Decorator to test for race conditions.</a> Towards the end, I discussed how that solution is only near-deterministic. In this article, I discuss a technique which is, I think, properly deterministic, but unfortunately less elegant. </p> <p> In short, it works by letting a <a href="https://en.wikipedia.org/wiki/Decorator_pattern">Decorator</a> synchronize reads. </p> <h3 id="867f586443864351a77bbeb8d2e00859"> The problem <a href="#867f586443864351a77bbeb8d2e00859">#</a> </h3> <p> In the previous article, I used words to describe the problem, but really, I should be showing, not telling. Here's a variation on the previous test that exemplifies the problem. </p> <p> <pre>[<span style="color:#2b91af;">Fact</span>] <span style="color:blue;">public</span>&nbsp;<span style="color:blue;">async</span>&nbsp;<span style="color:#2b91af;">Task</span>&nbsp;<span style="font-weight:bold;color:#74531f;">NoOverbookingRace</span>() { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">date</span>&nbsp;=&nbsp;<span style="color:#2b91af;">DateTime</span>.Now.Date.<span style="font-weight:bold;color:#74531f;">AddDays</span>(1).<span style="font-weight:bold;color:#74531f;">AddHours</span>(18.5); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">using</span>&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">service</span>&nbsp;=&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">RestaurantService</span>(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">using</span>&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">slowService</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">from</span>&nbsp;repo&nbsp;<span style="color:blue;">in</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">service</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">select</span>&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">SlowReservationsRepository</span>(<span style="color:#2b91af;">TimeSpan</span>.<span style="color:#74531f;">FromMilliseconds</span>(100),&nbsp;repo); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">task1</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">slowService</span>.<span style="font-weight:bold;color:#74531f;">PostReservation</span>(<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">ReservationDtoBuilder</span>() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithDate</span>(<span style="font-weight:bold;color:#1f377f;">date</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithQuantity</span>(10) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Build</span>()); &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">Delay</span>(<span style="color:#2b91af;">TimeSpan</span>.<span style="color:#74531f;">FromSeconds</span>(1)); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">task2</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">slowService</span>.<span style="font-weight:bold;color:#74531f;">PostReservation</span>(<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">ReservationDtoBuilder</span>() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithDate</span>(<span style="font-weight:bold;color:#1f377f;">date</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithQuantity</span>(10) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Build</span>()); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">actual</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">WhenAll</span>(<span style="font-weight:bold;color:#1f377f;">task1</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">task2</span>); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Assert</span>.<span style="color:#74531f;">Single</span>( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">actual</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>.StatusCode&nbsp;==&nbsp;<span style="color:#2b91af;">HttpStatusCode</span>.InternalServerError); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">ok</span>&nbsp;=&nbsp;<span style="color:#2b91af;">Assert</span>.<span style="color:#74531f;">Single</span>(<span style="font-weight:bold;color:#1f377f;">actual</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>.IsSuccessStatusCode); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Check&nbsp;that&nbsp;the&nbsp;reservation&nbsp;was&nbsp;actually&nbsp;created:</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">resp</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">service</span>.<span style="font-weight:bold;color:#74531f;">GetReservation</span>(<span style="font-weight:bold;color:#1f377f;">ok</span>.Headers.Location); &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">resp</span>.<span style="font-weight:bold;color:#74531f;">EnsureSuccessStatusCode</span>(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">reservation</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">resp</span>.<span style="font-weight:bold;color:#74531f;">ParseJsonContent</span>&lt;<span style="color:#2b91af;">ReservationDto</span>&gt;(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Assert</span>.<span style="color:#74531f;">Equal</span>(10,&nbsp;<span style="font-weight:bold;color:#1f377f;">reservation</span>.Quantity); }</pre> </p> <p> Apart from a single new line of code, this is is identical to the test shown in the previous article. The added line is the <code>Task.Delay</code> between <code>task1</code> and <code>task2</code>. </p> <p> What's the point of adding this delay there? Only to <em>demonstrate</em> a problem. That's one of the situations I described in the previous article: Even though the test starts both tasks without awaiting them, they aren't guaranteed to run in parallel. Both start as soon as they're created, so <code>task2</code> is going to be ever so slightly behind <code>task1</code>. What happens if there's a delay between the creation of these two tasks? </p> <p> Here I've explicitly introduced such a delay for demonstration purposes, but such a delay could happen on a real system for a number of reasons, including garbage collection, thread starvation, the OS running a higher-priority task, etc. </p> <p> Why might that pause matter? </p> <p> Because it may produce <a href="https://en.wikipedia.org/wiki/False_positives_and_false_negatives">false negatives</a>. Imagine a situation where there's <em>no</em> transaction control; where there's no <a href="https://learn.microsoft.com/dotnet/api/system.transactions.transactionscope">TransactionScope</a> around the database interactions. If the pause is long enough, the tasks effectively run in sequence (instead of in parallel), in which case the system correctly rejects the second attempt. </p> <p> This is even when using the <code>SlowReservationsRepository</code> Decorator. </p> <p> How long does the pause need to be before this happens? </p> <p> As described in the previous article, with a configured delay of 100 ms for the <code>SlowReservationsRepository</code>, creating a new reservation is delayed by 300 ms. This bears out. Experimenting on my own machine, if I change that explicit, artificial delay to 300 ms, and remove the transaction control, the test sometimes fails, and sometimes passes. With the above one-second delay, the test always passes, even when transaction control is missing. </p> <p> You could decide that a 300 ms pause at just the worst possible time is so unlikely that you're willing to simply accept those odds. I would probably be, too. Still, <a href="/2018/11/12/what-to-test-and-not-to-test">what to test, and what not to test</a> is a function of context. You may find yourself in a context where that's not good enough. What other options are there? </p> <h3 id="702b62450cb24cdc95f384b15d80a07c"> Synchronizing Decorator <a href="#702b62450cb24cdc95f384b15d80a07c">#</a> </h3> <p> What you <em>really</em> need to reproduce the race condition is to synchronize the database reads. If you could make sure that the Repository only returns data when enough reads have been performed, you can deterministically reproduce the problem. </p> <p> Again, start with a Decorator. This time, build into it a way to synchronize reads. </p> <p> <pre><span style="color:blue;">internal</span>&nbsp;<span style="color:blue;">sealed</span>&nbsp;<span style="color:blue;">class</span>&nbsp;<span style="color:#2b91af;">SynchronizedReaderRepository</span>&nbsp;:&nbsp;<span style="color:#2b91af;">IReservationsRepository</span> { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:blue;">readonly</span>&nbsp;<span style="color:#2b91af;">CountdownEvent</span>&nbsp;countdownEvent&nbsp;=&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">CountdownEvent</span>(2); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">public</span>&nbsp;<span style="color:#2b91af;">SynchronizedReaderRepository</span>(<span style="color:#2b91af;">IReservationsRepository</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">inner</span>) &nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Inner&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">inner</span>; &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">public</span>&nbsp;<span style="color:#2b91af;">IReservationsRepository</span>&nbsp;Inner&nbsp;{&nbsp;<span style="color:blue;">get</span>;&nbsp;}</pre> </p> <p> Here I've used a <a href="https://learn.microsoft.com/dotnet/api/system.threading.countdownevent">CountdownEvent</a> object to ensure that reads only progress when the countdown reaches zero. It's possible that more appropriate threading APIs exist, but this serves well as a proof of concept. </p> <p> The method you need to synchronize is <code>ReadReservations</code>, so you can leave all the other methods to delegate to <code>Inner</code>. Only <code>ReadReservations</code> is special. </p> <p> <pre><span style="color:blue;">public</span>&nbsp;<span style="color:blue;">async</span>&nbsp;<span style="color:#2b91af;">Task</span>&lt;<span style="color:#2b91af;">IReadOnlyCollection</span>&lt;<span style="color:#2b91af;">Reservation</span>&gt;&gt;&nbsp;<span style="font-weight:bold;color:#74531f;">ReadReservations</span>( &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">int</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">restaurantId</span>, &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">DateTime</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">min</span>, &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">DateTime</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">max</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">result</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;Inner&nbsp;.<span style="font-weight:bold;color:#74531f;">ReadReservations</span>(<span style="font-weight:bold;color:#1f377f;">restaurantId</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">min</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">max</span>); &nbsp;&nbsp;&nbsp;&nbsp;countdownEvent.<span style="font-weight:bold;color:#74531f;">Signal</span>(); &nbsp;&nbsp;&nbsp;&nbsp;countdownEvent.<span style="font-weight:bold;color:#74531f;">Wait</span>(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">result</span>; }</pre> </p> <p> This implementation also starts by delegating to <code>Inner</code>, but before it returns the <code>result</code>, it signals the <code>countdownEvent</code> and blocks the thread by waiting on the <code>countdownEvent</code>. Only when both threads have signalled it does the counter reach zero, and the methods may proceed. </p> <p> If we assume that, while the test is running, no other calls to <code>ReadReservations</code> is made, this guarantees that both threads receive the same answer. This will make both competing threads come to the answer that they can accept the reservation. If no transaction control is in place, the system will overbook the requested time slot. </p> <h3 id="008987e40f7e4d5baeda6b245d086a43"> Testing with the synchronizing Repository <a href="#008987e40f7e4d5baeda6b245d086a43">#</a> </h3> <p> The test that uses <code>SynchronizedReaderRepository</code> is almost identical to the previous test. </p> <p> <pre>[<span style="color:#2b91af;">Fact</span>] <span style="color:blue;">public</span>&nbsp;<span style="color:blue;">async</span>&nbsp;<span style="color:#2b91af;">Task</span>&nbsp;<span style="font-weight:bold;color:#74531f;">NoOverbookingRace</span>() { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">date</span>&nbsp;=&nbsp;<span style="color:#2b91af;">DateTime</span>.Now.Date.<span style="font-weight:bold;color:#74531f;">AddDays</span>(1).<span style="font-weight:bold;color:#74531f;">AddHours</span>(18.5); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">using</span>&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">service</span>&nbsp;=&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">RestaurantService</span>(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">using</span>&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">syncedService</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">service</span>.<span style="font-weight:bold;color:#74531f;">Select</span>(<span style="font-weight:bold;color:#1f377f;">repo</span>&nbsp;=&gt;&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">SynchronizedReaderRepository</span>(<span style="font-weight:bold;color:#1f377f;">repo</span>)); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">task1</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">syncedService</span>.<span style="font-weight:bold;color:#74531f;">PostReservation</span>(<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">ReservationDtoBuilder</span>() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithDate</span>(<span style="font-weight:bold;color:#1f377f;">date</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithQuantity</span>(10) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Build</span>()); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">task2</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">syncedService</span>.<span style="font-weight:bold;color:#74531f;">PostReservation</span>(<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">ReservationDtoBuilder</span>() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithDate</span>(<span style="font-weight:bold;color:#1f377f;">date</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithQuantity</span>(10) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Build</span>()); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">actual</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">WhenAll</span>(<span style="font-weight:bold;color:#1f377f;">task1</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">task2</span>); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Assert</span>.<span style="color:#74531f;">Single</span>( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">actual</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>.StatusCode&nbsp;==&nbsp;<span style="color:#2b91af;">HttpStatusCode</span>.InternalServerError); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">ok</span>&nbsp;=&nbsp;<span style="color:#2b91af;">Assert</span>.<span style="color:#74531f;">Single</span>(<span style="font-weight:bold;color:#1f377f;">actual</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">msg</span>.IsSuccessStatusCode); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Check&nbsp;that&nbsp;the&nbsp;reservation&nbsp;was&nbsp;actually&nbsp;created:</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">resp</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">service</span>.<span style="font-weight:bold;color:#74531f;">GetReservation</span>(<span style="font-weight:bold;color:#1f377f;">ok</span>.Headers.Location); &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">resp</span>.<span style="font-weight:bold;color:#74531f;">EnsureSuccessStatusCode</span>(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">reservation</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">resp</span>.<span style="font-weight:bold;color:#74531f;">ParseJsonContent</span>&lt;<span style="color:#2b91af;">ReservationDto</span>&gt;(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Assert</span>.<span style="color:#74531f;">Equal</span>(10,&nbsp;<span style="font-weight:bold;color:#1f377f;">reservation</span>.Quantity); }</pre> </p> <p> Contrary to using the slow Repository, this test doesn't allow false negatives. If transaction control is missing from the System Under Test (SUT), this test fails. And it passes when transaction control is in place. </p> <h3 id="c261a239b7a44a819eed3d4526adefed"> Disadvantages <a href="#c261a239b7a44a819eed3d4526adefed">#</a> </h3> <p> That sounds great, so why not just do this, instead of using a delaying Decorator? Because, as usual, there are trade-offs involved. This kind of solution comes with some disadvantages that are worth taking into account. </p> <p> In short, this could make the test more <a href="http://xunitpatterns.com/Fragile%20Test.html">fragile</a>. As shown above, <code>SynchronizedReaderRepository</code> makes a specific assumption. It assumes that it needs to synchronize exactly two parallel readers. One problem with this is that this may be coupled to exactly one test. If you had other tests, you'd need to write a new Decorator, or generalize this one in some way. </p> <p> Another problem is that this makes the test sensitive to changes in the SUT. What if a code change introduces a new call to <code>ReadReservations</code>? If so, the <code>countdownEvent</code> may unblock the threads too soon. One such change may be that the SUT decides to also query for surrounding times slots. You might be able to make <code>SynchronizedReaderRepository</code> robust against such changes by keeping a dictionary of synchronization objects (such as the above <code>CountdownEvent</code>) per argument set, but that clearly complicates the implementation. </p> <p> And even so, it doesn't protect against identical 'double reads', even though these may be less likely to happen. </p> <p> This Decorator is also vulnerable to caching. If you have a read-through cache that wraps around <code>SynchronizedReaderRepository</code>, only the first query may get to it, which would then cause it to block forever. Perhaps, again, you could fix this with the <a href="https://learn.microsoft.com/dotnet/api/system.threading.countdownevent.wait">Wait</a> overload that takes a timeout value. </p> <p> That said, if you cache reads, the pessimistic locking that <a href="https://learn.microsoft.com/dotnet/api/system.transactions.transactionscope">TransactionScope</a> uses isn't going to work. You could, perhaps, address that concern with optimistic concurrency, but that comes with its own problems. </p> <h3 id="d7dcb5c138e1451499f5c2c2162fb416"> Conclusion <a href="#d7dcb5c138e1451499f5c2c2162fb416">#</a> </h3> <p> You can address race conditions in various ways, but synchronization has been around for a long time. Not only can you use synchronization primitives and APIs to make your code thread-safe, you can also use them to deterministically reproduce race conditions, or to test that such a bug is no longer present in the system. </p> <p> I don't want to claim that this is universally possible, but if you run into such problems, it's at least worth considering if you could take advantage of synchronization to reproduce a problem. </p> <p> Of course, the implication is that you understand what the problem is. This is often the hardest part of dealing with race conditions, and the ideas described in this article don't help with that. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/08/04/testing-races-with-a-synchronizing-decorator Song recommendations with F# agents https://blog.ploeh.dk/2025/07/28/song-recommendations-with-f-agents/ Mon, 28 Jul 2025 08:09:00 UTC <div id="post"> <p> <em>MailboxProcessors as small Recawr Sandwiches.</em> </p> <p> This article is part of a series named <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">Alternative ways to design with functional programming</a>. As the title implies, over a multitude of articles, I present various alternatives for applying functional programming to a particular problem. When I present the <a href="/2020/03/02/impureim-sandwich">Impureim Sandwich</a> design pattern, the most common reaction is: <em>What if you need to make additional, impure reads in the middle of an algorithm?</em> </p> <p> This article series looks at alternatives when this (in my experience rare) requirement seems inescapable. A <a href="/2025/07/14/song-recommendations-with-pipes-and-filters">previous article</a> outlined a general alternative: Use some kind of pipes-and-filters architecture to turn an undisciplined <a href="https://martinfowler.com/eaaCatalog/transactionScript.html">Transaction Script</a> into a composition of 'filters', where each filter is a self-contained <a href="/2025/01/13/recawr-sandwich">Recawr Sandwich</a>. </p> <p> Depending on the specific technology choices you make, you may encounter various terminology related to this kind of architecture. Filters may also be called <em>actors</em>, <em>message handlers</em>, or, as is the case in this article, <em>agents</em>. For a consistent pattern language, see <a href="/ref/eip">Enterprise Integration Patterns</a>. </p> <p> The code shown here is taken from the <em>fsharp-agents</em> branch of the example code Git repository. </p> <h3 id="5950d36c50c5467ba893b1b80c2b5034"> Async instead of Task <a href="#5950d36c50c5467ba893b1b80c2b5034">#</a> </h3> <p> The <a href="https://fsharp.org/">F#</a> base library comes with a class called <a href="https://fsharp.github.io/fsharp-core-docs/reference/fsharp-control-fsharpmailboxprocessor-1.html">MailboxProcessor</a>, often called an <em>agent</em>. It's an in-memory message handler that can run in the background of other tasks, pulling messages off an internal queue one at a time. </p> <p> It's been around for such a long time that its API is based on <a href="https://fsharp.github.io/fsharp-core-docs/reference/fsharp-control-fsharpasync-1.html">Async&lt;T&gt;</a>, which precedes the now-ubiquitous <a href="https://learn.microsoft.com/dotnet/api/system.threading.tasks.task-1">Task&lt;TResult&gt;</a>. While conversions exist, I thought it'd make the example code simpler if I first redefined the <code>SongService</code> interface to return <code>Async</code>-based results. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:#2b91af;">SongService</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">abstract</span>&nbsp;<span style="font-weight:bold;color:#74531f;">GetTopListenersAsync</span>&nbsp;:&nbsp;songId&nbsp;:&nbsp;<span style="color:#2b91af;">int</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Async</span>&lt;<span style="color:#2b91af;">IReadOnlyCollection</span>&lt;<span style="color:#2b91af;">User</span>&gt;&gt; &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">abstract</span>&nbsp;<span style="font-weight:bold;color:#74531f;">GetTopScrobblesAsync</span>&nbsp;:&nbsp;userName&nbsp;:&nbsp;<span style="color:#2b91af;">string</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Async</span>&lt;<span style="color:#2b91af;">IReadOnlyCollection</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;&gt;</pre> </p> <p> Keep in mind that, despite lack of the <a href="/2015/08/03/idiomatic-or-idiosyncratic">idiomatic</a> <code>I</code> prefix, this is an interface, not an abstract class. (It would have had to have the <code>[&lt;AbstractClass&gt;]</code> attribute to be an abstract class.) </p> <p> This is minor change that only affected a few lines of code where I had to change from <a href="https://learn.microsoft.com/dotnet/fsharp/language-reference/task-expressions">task expressions</a> to <a href="https://learn.microsoft.com/dotnet/fsharp/language-reference/async-expressions">async expressions</a>. </p> <h3 id="49e02be04bbc453db9c6a2c1df8a142e"> Gather own scrobbles <a href="#49e02be04bbc453db9c6a2c1df8a142e">#</a> </h3> <p> As was also the case in the <a href="/2025/07/21/song-recommendations-with-c-reactive-extensions">previous article</a>, we may as well start at the beginning of the algorithm. Given a user name, we'd like to find that user's top scrobbles. Once we've found them, we'd like to post them to an agent. Since F# agents are message-based, we must define an appropriate message type. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#2b91af;">ScrobbleMessage</span>&nbsp;=&nbsp;<span style="color:#2b91af;">Scrobble</span>&nbsp;<span style="color:blue;">of</span>&nbsp;<span style="color:#2b91af;">Scrobble</span>&nbsp;|&nbsp;<span style="color:#2b91af;">EndOfStream</span></pre> </p> <p> If you recall the <a href="/2025/07/21/song-recommendations-with-c-reactive-extensions">previous article</a>, with <a href="https://github.com/dotnet/reactive">Reactive Extensions for .NET</a>, you can use the <a href="https://learn.microsoft.com/dotnet/api/system.iobserver-1.oncompleted">OnCompleted</a> method to signal the end of a stream. Such a method isn't available for an F# agent, because an agent is a consumer of messages, rather than a stream of values. For that reason, a <code>ScrobbleMessage</code> may either be a <code>Scrobble</code> or an <code>EndOfStream</code>. </p> <p> With the message definition in place, you can define the first step of the algorithm like this: </p> <p> <pre><span style="color:green;">//&nbsp;string&nbsp;-&gt;&nbsp;SongService&nbsp;-&gt;&nbsp;MailboxProcessor&lt;ScrobbleMessage&gt;&nbsp;-&gt;&nbsp;Async&lt;unit&gt;</span> <span style="color:blue;">let</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#74531f;">gatherOwnScrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;:&nbsp;<span style="color:#2b91af;">SongService</span>)&nbsp;(<span style="color:#1f377f;">channel</span>&nbsp;:&nbsp;<span style="color:#2b91af;">MailboxProcessor</span>&lt;_&gt;)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">async</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let!</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>.<span style="font-weight:bold;color:#74531f;">GetTopScrobblesAsync</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Pure</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobblesSnapshot</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">sortByDescending</span>&nbsp;_.ScrobbleCount &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">truncate</span>&nbsp;100 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">map</span>&nbsp;<span style="color:#2b91af;">Scrobble</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">iter</span>&nbsp;<span style="color:#1f377f;">channel</span>.<span style="font-weight:bold;color:#74531f;">Post</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobblesSnapshot</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#1f377f;">channel</span>.<span style="font-weight:bold;color:#74531f;">Post</span>&nbsp;<span style="color:#2b91af;">EndOfStream</span>&nbsp;}</pre> </p> <p> The <code>gatherOwnScrobbles</code> action isn't itself an agent. Rather, it takes one as input, to which it posts messages. Notice that the operation returns an <code>Async&lt;unit&gt;</code> value. In other words, it doesn't really return anything, but rather posts messages to the injected <code>channel</code>. </p> <p> Like in the <a href="/2025/07/21/song-recommendations-with-c-reactive-extensions">previous article</a>, once all scrobbles are posted, <code>gatherOwnScrobbles</code> indicates the end of the stream by posting a final <code>EndOfStream</code> message. This still works in this implementation, since F# agents (as far as I've been able ascertain) handle messages in order. If you're using a distributed messaging framework based on a message bus, and possibly handlers running on multiple machines, you can't always assume this to be the case. As I wrote in <a href="/2025/07/14/song-recommendations-with-pipes-and-filters">Song recommendations with pipes and filters</a>, you'll need to extrapolate from both this and the previous article in such cases. This is where a pattern language as presented in <a href="/ref/eip">Enterprise Integration Patterns</a> may come in handy. Perhaps you need a <a href="https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageSequence.html">Message Sequence</a> in this case. </p> <p> Be that as it may, the <code>gatherOwnScrobbles</code> action is a small <a href="/2025/01/13/recawr-sandwich">Recawr Sandwich</a> with clearly delineated steps. </p> <p> The natural next step is to implement a MailboxProcessor that can receive those scrobble messages. </p> <h3 id="4ec7b8d1fd6a4158ac86dc83621e8082"> Gather other listeners <a href="#4ec7b8d1fd6a4158ac86dc83621e8082">#</a> </h3> <p> To handle the messages posted by <code>gatherOwnScrobbles</code> we'll create an agent. This one, however, isn't going to complete the algorithm. Rather, it's going to publish even more messages that yet another agent may deal with. For that, we need another message type: </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#2b91af;">UserMessage</span>&nbsp;=&nbsp;<span style="color:#2b91af;">User</span>&nbsp;<span style="color:blue;">of</span>&nbsp;<span style="color:#2b91af;">User</span>&nbsp;|&nbsp;<span style="color:#2b91af;">EndOfStream</span></pre> </p> <p> We see what starts to look like a pattern: A 'payload' case, and a case to indicate that no more message will be coming. </p> <p> The following action creates an agent that handles scrobble messages and publishes user messages: </p> <p> <pre><span style="color:green;">//&nbsp;SongService&nbsp;-&gt;&nbsp;MailboxProcessor&lt;UserMessage&gt;&nbsp;-&gt;&nbsp;MailboxProcessor&lt;ScrobbleMessage&gt;</span> <span style="color:blue;">let</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#74531f;">gatherOtherListeners</span> &nbsp;&nbsp;&nbsp;&nbsp;(<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;:&nbsp;<span style="color:#2b91af;">SongService</span>)&nbsp;(<span style="color:#1f377f;">channel</span>&nbsp;:&nbsp;<span style="color:#2b91af;">MailboxProcessor</span>&lt;_&gt;)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">MailboxProcessor</span>.<span style="font-weight:bold;color:#74531f;">Start</span>&nbsp;&lt;|&nbsp;<span style="color:blue;">fun</span>&nbsp;<span style="color:#1f377f;">inbox</span>&nbsp;<span style="color:blue;">-&gt;</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="color:blue;">rec</span>&nbsp;<span style="color:#74531f;">loop</span>&nbsp;()&nbsp;=&nbsp;<span style="color:blue;">async</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let!</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">message</span>&nbsp;=&nbsp;<span style="color:#1f377f;">inbox</span>.<span style="font-weight:bold;color:#74531f;">Receive</span>&nbsp;() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">match</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">message</span>&nbsp;<span style="color:blue;">with</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">Scrobble</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobble</span>&nbsp;<span style="color:blue;">-&gt;</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let!</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListeners</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>.<span style="font-weight:bold;color:#74531f;">GetTopListenersAsync</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobble</span>.Song.Id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Pure</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListenersSnapshot</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListeners</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">filter</span>&nbsp;(<span style="color:blue;">fun</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">u</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">u</span>.TotalScrobbleCount&nbsp;&gt;=&nbsp;10_000) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">sortByDescending</span>&nbsp;_.TotalScrobbleCount &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">truncate</span>&nbsp;20 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">map</span>&nbsp;<span style="color:#2b91af;">User</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">iter</span>&nbsp;<span style="color:#1f377f;">channel</span>.<span style="font-weight:bold;color:#74531f;">Post</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListenersSnapshot</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">loop</span>&nbsp;() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">ScrobbleMessage</span>.<span style="color:#2b91af;">EndOfStream</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#1f377f;">channel</span>.<span style="font-weight:bold;color:#74531f;">Post</span>&nbsp;<span style="color:#2b91af;">EndOfStream</span>&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">loop</span>&nbsp;()</pre> </p> <p> If you can look past some of the infrastructure required to initialize and implement the agent (<code>MailboxProcessor</code>), the main message handler is, once again, a small Recawr Sandwich. The other case in the <code>match</code> expression maps one <code>EndOfStream</code> case to another <code>EndOfStream</code> case. Notice that this case does <em>not</em> recursively call <code>loop</code>. This means that once the agent receives an <code>EndOfStream</code> message, it stops all further message processing. </p> <p> You may have noticed that the <code>loop</code> starts with an 'unmarked' impure step to receive a message. Once a message arrives, it matches on the message. You may argue that there seems to be more than one impure step in the sandwich, but as I've previously outlined, <a href="/2023/10/09/whats-a-sandwich">sometimes a sandwich has more that three layers</a>. </p> <p> I could have compressed the code that receives and dispatches the message to a single line of code: </p> <p> <pre><span style="color:blue;">match!</span>&nbsp;<span style="color:#1f377f;">input</span>.<span style="font-weight:bold;color:#74531f;">Receive</span>&nbsp;()&nbsp;<span style="color:blue;">with</span></pre> </p> <p> I felt, however, that for readers who aren't familiar with F# agents, it would help to instead make things more explicit by having a named <code>message</code> value in the code. It's an example of using an explicit variable for readability purposes. </p> <p> A third agent, created by <code>gatherOtherScrobbles</code>, handles the messages published by <code>gatherOtherListeners</code> by publishing even more song messages. We'll get back to that message type in a moment, but the agent looks similar to the one shown above. You may consult the Git repository if you're curious about the details. </p> <h3 id="a2b09f2657e2497f955927fa13adc438"> Collecting the recommendations <a href="#a2b09f2657e2497f955927fa13adc438">#</a> </h3> <p> The final agent is a bit different, because it needs to do two things: </p> <ul> <li>Handle song messages</li> <li>Return the recommendations once they're ready</li> </ul> <p> Because of that extra responsibility, the message type isn't a two-way discriminated union. Instead, it has a third case that we haven't yet seen. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#2b91af;">SongMessage</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">Song</span>&nbsp;<span style="color:blue;">of</span>&nbsp;<span style="color:#2b91af;">Song</span> &nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">EndOfStream</span> &nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">Fetch</span>&nbsp;<span style="color:blue;">of</span>&nbsp;<span style="color:#2b91af;">AsyncReplyChannel</span>&lt;<span style="color:#2b91af;">Option</span>&lt;<span style="color:#2b91af;">IReadOnlyCollection</span>&lt;<span style="color:#2b91af;">Song</span>&gt;&gt;&gt;</pre> </p> <p> The <code>Song</code> and <code>EndOfStream</code> cases are similar to what we've already seen. These are the two messages that the <code>gatherOtherScrobbles</code> agent publishes. </p> <p> What does the third case, <code>Fetch</code>, do? It looks odd, with its <code>AsyncReplyChannel</code> payload. In a moment, you'll see how it's used, but essentially, this is how F# agents support the <a href="https://www.enterpriseintegrationpatterns.com/patterns/messaging/RequestReply.html">Request-Reply</a> pattern. Let's see it all in action: </p> <p> <pre><span style="color:green;">//&nbsp;unit&nbsp;-&gt;&nbsp;MailboxProcessor&lt;SongMessage&gt;</span> <span style="color:blue;">let</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#74531f;">collectRecommendations</span>&nbsp;()&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">MailboxProcessor</span>.<span style="font-weight:bold;color:#74531f;">Start</span>&nbsp;&lt;|&nbsp;<span style="color:blue;">fun</span>&nbsp;<span style="color:#1f377f;">input</span>&nbsp;<span style="color:blue;">-&gt;</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="color:blue;">rec</span>&nbsp;<span style="color:#74531f;">loop</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">recommendations</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">isDone</span>&nbsp;=&nbsp;<span style="color:blue;">async</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let!</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">message</span>&nbsp;=&nbsp;<span style="color:#1f377f;">input</span>.<span style="font-weight:bold;color:#74531f;">Receive</span>&nbsp;() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">match</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">message</span>&nbsp;<span style="color:blue;">with</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">Song</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">song</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">loop</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">song</span>&nbsp;<span style="color:#2b91af;">::</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">recommendations</span>)&nbsp;<span style="color:blue;">false</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">SongMessage</span>.<span style="color:#2b91af;">EndOfStream</span>&nbsp;<span style="color:blue;">-&gt;</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">recommendations</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">recommendations</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">List</span>.<span style="color:#74531f;">sortByDescending</span>&nbsp;_.Rating &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">List</span>.<span style="color:#74531f;">truncate</span>&nbsp;200 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">loop</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">recommendations</span>&nbsp;<span style="color:blue;">true</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">Fetch</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">replyChannel</span>&nbsp;<span style="color:blue;">-&gt;</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">if</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">isDone</span>&nbsp;<span style="color:blue;">then</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">replyChannel</span>.<span style="font-weight:bold;color:#74531f;">Reply</span>&nbsp;(<span style="color:#2b91af;">Some</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">recommendations</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">else</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">replyChannel</span>.<span style="font-weight:bold;color:#74531f;">Reply</span>&nbsp;<span style="color:#2b91af;">None</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">loop</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">recommendations</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">isDone</span>&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">loop</span>&nbsp;[]&nbsp;<span style="color:blue;">false</span></pre> </p> <p> The purpose of this agent is to collect all the songs that the previous agent recommended. Once it receives an <code>EndOfStream</code> message, it sorts the songs and keeps only the top 200. </p> <p> Note that the recursive <code>loop</code> action takes two parameters, <code>recommendations</code> and <code>isDone</code>. The loops starts with an empty song list and the flag set to <code>false</code>. When a new <code>Song</code> arrives, the loop prepends the song onto the song list and recurses. Notice that in that case, the flag remains <code>false</code>. </p> <p> Only when an <code>EndOfStream</code> message arrives does the agent calculate the final recommendations. Afterwards, it recursively calls <code>loop</code> with the flag raised (set to <code>true</code>). Notice, however, that the agent doesn't stop handling messages, like the other agents do when encountering an <code>EndOfStream</code> message. </p> <p> At any time during execution, a <code>Fetch</code> message may arrive. This is a request to return the recommendations, if they're ready. In that case, the <code>recommendations</code> are wrapped in a <code>Some</code> case and returned. If the recommendations are not yet ready, <code>None</code> is returned instead. </p> <p> This enables the overall, blocking method to poll for the recommendations until they are ready. You'll see how this works in a moment. </p> <h3 id="729573092e814326a356423e87078f5e"> Polling for results <a href="#729573092e814326a356423e87078f5e">#</a> </h3> <p> The <code>MailboxProcessor</code> class defines a <code>PostAndAsyncReply</code> method that does, indeed, fit the <code>Fetch</code> case of the above <code>SongMessage</code> type. This enables us to implement a polling mechanism like this: </p> <p> <pre><span style="color:blue;">let</span>&nbsp;<span style="color:blue;">rec</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#74531f;">poll</span>&nbsp;(<span style="color:#1f377f;">agent</span>&nbsp;:&nbsp;<span style="color:#2b91af;">MailboxProcessor</span>&lt;_&gt;)&nbsp;=&nbsp;<span style="color:blue;">task</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">match!</span>&nbsp;<span style="color:#1f377f;">agent</span>.<span style="font-weight:bold;color:#74531f;">PostAndAsyncReply</span>&nbsp;<span style="color:#2b91af;">Fetch</span>&nbsp;<span style="color:blue;">with</span> &nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">Some</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">result</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">return</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">result</span> &nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<span style="color:#2b91af;">None</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">poll</span>&nbsp;<span style="color:#1f377f;">agent</span>&nbsp;}</pre> </p> <p> This recursive action uses <code>PostAndAsyncReply</code> to keep polling its agent until it receives a useful reply. Since this code is mostly meant for illustration purposes, I've allowed myself a few shortcuts. </p> <p> First, this effectively implements a busy loop. Whenever it receives a <code>None</code> reply, it immediately recurses to try again. A more reasonable implementation may put a small delay there, but I think that finding the optimal delay time may be a matter of experimentation. After all, if you're concerned with performance, <a href="https://ericlippert.com/2012/12/17/performance-rant/">race your horses</a>. Given that this is demo code, I don't have any real horses to race, so I'm not going to try. From observation, however, it doesn't seem as though the tests, at least, run any slower despite the tight loop. </p> <p> Secondly, the <code>poll</code> loop keeps going until it receives a useful response. What if that never happens? A more robust implementation should implement some kind of timeout or ceiling that enables it to give up if it's been running for too long. </p> <p> Apart from all that, how does the <code>poll</code> action even type-check? On a <code><span style="color:#2b91af;">MailboxProcessor</span>&lt;<span style="color:#2b91af;">'Msg</span>&gt;</code> object, the <code>PostAndAsyncReply</code> method has this type: </p> <p> <pre>(<span style="color:#2b91af;">AsyncReplyChannel</span>&lt;<span style="color:#2b91af;">'Reply</span>&gt; -&gt; <span style="color:#2b91af;">'Msg</span>) <span style="color:blue;">-&gt;</span> <span style="color:#2b91af;">Async</span>&lt;<span style="color:#2b91af;">'Reply</span>&gt;</pre> </p> <p> ignoring an optional timeout parameter. </p> <p> The above <code>Fetch</code> case constructor fits the type of <code>PostAndAsyncReply</code>, since it has the type </p> <p> <pre><span style="color:#2b91af;">AsyncReplyChannel</span>&lt;<span style="color:#2b91af;">Option</span>&lt;<span style="color:#2b91af;">IReadOnlyCollection</span>&lt;<span style="color:#2b91af;">Song</span>&gt;&gt;&gt; <span style="color:blue;">-&gt;</span> <span style="color:#2b91af;">SongMessage</span></pre> </p> <p> This means that we can infer <code>'Reply</code> to be <code><span style="color:#2b91af;">Option</span>&lt;<span style="color:#2b91af;">IReadOnlyCollection</span>&lt;<span style="color:#2b91af;">Song</span>&gt;&gt;</code>. It also means that <code>'Msg</code> must be <code>SongMessage</code>, and again we can infer that the <code>agent</code> parameter has the type <code><span style="color:#2b91af;">MailboxProcessor</span>&lt;<span style="color:#2b91af;">SongMessage</span>&gt;</code>. </p> <h3 id="d92982c8ced649ceaf2b8c42c6092f4b"> Composition <a href="#d92982c8ced649ceaf2b8c42c6092f4b">#</a> </h3> <p> With all components ready, we can now compose them as a blocking method. Notice that, in the following, the <code>GetRecommendationsAsync</code> method hasn't changed type or observable behaviour. The change from <code>Task</code> to <code>Async</code> (described above) required some trivial changes to <code>FakeSongService</code>, but apart from that, I had to change no test code to make this refactoring. </p> <p> As a first attempt, we may compose the agents using the idiomatic left-to-right pipeline operator, like this: </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:#2b91af;">RecommendationsProvider</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;:&nbsp;<span style="color:#2b91af;">SongService</span>)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">member</span>&nbsp;_.<span style="font-weight:bold;color:#74531f;">GetRecommendationsAsync</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;=&nbsp;<span style="color:#74531f;">collectRecommendations</span>&nbsp;() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">task</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">do!</span>&nbsp;<span style="color:#1f377f;">collect</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#74531f;">gatherOtherScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#74531f;">gatherOtherListeners</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#74531f;">gatherOwnScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">poll</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;}</pre> </p> <p> First, the <code>task</code> expression starts all the agents, and then proceeds to <code>poll</code> the <code>collect</code> agent until it arrives at a result. </p> <p> This passes all tests, but has at least two problems. One problem is that the composition seems backwards. It looks as though the process starts with <code>collect</code>, then proceeds to <code>gatherOtherScrobbles</code>, and so on. In reality, it's the other way around. You should really understand the composition as being defined 'bottom-up', or right-to-left, if we put it on a single line. We'll return to the other problem in a moment, but let's first see if we can do something about this one. </p> <p> My first attempt to fix this problem was to try to use the reverse pipeline operator <code>&lt;|</code>, but due to precedence rules, it didn't work without parentheses. And if we need parentheses anyway, there's no reason to use the reverse pipeline operator. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:#2b91af;">RecommendationsProvider</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;:&nbsp;<span style="color:#2b91af;">SongService</span>)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">member</span>&nbsp;_.<span style="font-weight:bold;color:#74531f;">GetRecommendationsAsync</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;=&nbsp;<span style="color:#74531f;">collectRecommendations</span>&nbsp;() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">task</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">do!</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">gatherOwnScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">gatherOtherListeners</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">gatherOtherScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#1f377f;">collect</span>))) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">poll</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;}</pre> </p> <p> This composition uses a slightly unorthodox code formatting style. Since <code>collect</code> is really nested inside of <code>gatherOtherScrobbles</code>, it should really have been indented to the right of it. Likewise, <code>gatherOtherScrobbles</code> is nested inside of <code>gatherOtherListeners</code>, and so on. A more idiomatic formatting of the code might be something like this: </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:#2b91af;">RecommendationsProvider</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;:&nbsp;<span style="color:#2b91af;">SongService</span>)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">member</span>&nbsp;_.<span style="font-weight:bold;color:#74531f;">GetRecommendationsAsync</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;=&nbsp;<span style="color:#74531f;">collectRecommendations</span>&nbsp;() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">task</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">do!</span>&nbsp;<span style="color:#74531f;">gatherOwnScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">gatherOtherListeners</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">gatherOtherScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;<span style="color:#1f377f;">collect</span>)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">poll</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;}</pre> </p> <p> This, however, blurs the semantics of the composition in favour of the mechanics of it. I don't consider it an improvement. </p> <p> All of this, however, turns out to be moot because of the other problem. The <code>MailboxProcessor</code> class implements <a href="https://learn.microsoft.com/dotnet/api/system.idisposable">IDisposable</a>, and to be good citizens, we ought to dispose of the objects once we're done with them. This is possible, but we're now back to the backwards order of composition. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:#2b91af;">RecommendationsProvider</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;:&nbsp;<span style="color:#2b91af;">SongService</span>)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">member</span>&nbsp;_.<span style="font-weight:bold;color:#74531f;">GetRecommendationsAsync</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;=&nbsp;<span style="color:blue;">task</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">use</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;=&nbsp;<span style="color:#74531f;">collectRecommendations</span>&nbsp;() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">use</span>&nbsp;<span style="color:#1f377f;">otherScrobbles</span>&nbsp;=&nbsp;<span style="color:#74531f;">gatherOtherScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;<span style="color:#1f377f;">collect</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">use</span>&nbsp;<span style="color:#1f377f;">otherListeners</span>&nbsp;=&nbsp;<span style="color:#74531f;">gatherOtherListeners</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;<span style="color:#1f377f;">otherScrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">do!</span>&nbsp;<span style="color:#74531f;">gatherOwnScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;<span style="color:#1f377f;">otherListeners</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return!</span>&nbsp;<span style="color:#74531f;">poll</span>&nbsp;<span style="color:#1f377f;">collect</span>&nbsp;}</pre> </p> <p> This may not be an entirely unsolvable problem, but this is where I'm no longer interested in pursuing this line of inquiry much further. Instead of those 'factory actions' that create and return agents, you could refactor each agent into a separate object that, when disposed of, also disposes of any inner agents it may contain. If so, you could again compose these objects as shown above, and only dispose of the outer object. </p> <h3 id="78e407aecd044d68bd16a8ae207ffa4c"> Evaluation <a href="#78e407aecd044d68bd16a8ae207ffa4c">#</a> </h3> <p> These various attempts to make the agents compose nicely, in a way that also works as self-documenting code, reveals that F# agents aren't as composable as <a href="https://reactivex.io/">ReactiveX</a>. To be fair, the <code>MailboxProcessor</code> class also predates Reactive Extensions, so we shouldn't blame it for not being as good as a more recent technology. </p> <p> One major problem is that agents don't compose naturally, <a href="/2025/03/03/reactive-monad">like IObservable&lt;T&gt; does</a>. I briefly considered whether it'd be possible to make <code><span style="color:#2b91af;">MailboxProcessor</span>&lt;'Msg&gt;</code> a <a href="/2022/03/28/monads">monad</a>, but armed with the knowledge of variance imparted by <a href="https://thinkingwithtypes.com/">Thinking with Types</a>, I quickly realized that the type is invariant. This is easily seen because one method, <code>Post</code>, is contravariant in <code>'Msg</code>, whereas most other methods are covariant. I'm using deliberately vague language, since there's no reason to calculate the kind of variance for all methods when you've already found two incompatible members. </p> <p> Another fly in the ointment is that the <code>collectRecommendations</code> action looks messy. As presented, it's not a pretty Impureim Sandwich. Most of the 'middle' message handling could be extracted to a pure function, were it not for the <code>Fetch</code> case. Calling <code>replyChannel.Reply</code> has a side effect, and while I know of refactorings that move side effects around, I'd need access to the <code>replyChannel</code> in order to impart that effect from somewhere else. This would still be possible if I returned an action to be invoked, but I don't see much point in that. In general, that request-reply API doesn't strike me as particularly functional. </p> <p> Based on all that griping, you may be wondering whether this kind of architecture is worth the trouble. Keep in mind, though, that some of the issues I just outlined is the result of the particular <code>MailboxProcessor</code> API. It's not a given that if you use some other message-based framework, you'll run into the same issues. </p> <p> You may also find the notion of posting messages to a chain of agents, only to poll one of them for the result, as carrying coals to Newcastle. Keep in mind, however, that the code presented here refactors a blocking method call that <a href="https://x.com/Tyrrrz/status/1493369905869213700">apparently takes about ten minutes to run to completion</a>. It's possible that I read too much into the situation, but I'm guessing that the 'real' code base that was the inspiration for the example code, doesn't actually block for ten minutes in order to return a result. Rather, I still speculate, it's probably a background batch job that produces persisted views; e.g. as JSON files. If so, you'd really just want to trigger a new batch job and let it run to completion in the background. In such a scenario, I'd find an asynchronous, message-based architecture suitable for the job. In that case, you'd need no polling loop. Rather, you serve a persisted view whenever anyone asks for it, and once in a while, that persisted view has been updated by the background process. </p> <h3 id="7c4b1d25b43c4fea9a969eefe7e04fcc"> Conclusion <a href="#7c4b1d25b43c4fea9a969eefe7e04fcc">#</a> </h3> <p> Compared to the <a href="/2025/07/21/song-recommendations-with-c-reactive-extensions">previous article</a>, which used Reactive Extensions to compose self-contained Recawr Sandwiches, using F# agents is a move towards a more standard kind of message-based architecture. Hopefully, it does a good enough job of illustrating how you can refactor an impure action into a composition of individual sandwiches, even if some of the details here are particular to F# agents. </p> <p> It's not necessarily always the best solution to the underlying problem being addressed in this article series, but it seems appropriate if the problem of large data sets is combined with long running time. If you can convert the overall problem to a fire-and-forget architecture, a message-based system may be suitable. </p> <p> If, on the other hand, you need to maintain the blocking nature of the operation, you may need to reach for the big, universal <a href="https://en.wikipedia.org/wiki/Law_of_the_instrument">hammer</a>. </p> <p> <strong>Next:</strong> <a href="/2025/08/11/song-recommendations-with-free-monads">Song recommendations with free monads</a>. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/07/28/song-recommendations-with-f-agents Song recommendations with C# Reactive Extensions https://blog.ploeh.dk/2025/07/21/song-recommendations-with-c-reactive-extensions/ Mon, 21 Jul 2025 13:54:00 UTC <div id="post"> <p> <em>Observables as small Recawr Sandwiches.</em> </p> <p> This article is part of a series titled <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">Alternative ways to design with functional programming</a>. In the <a href="/2025/07/14/song-recommendations-with-pipes-and-filters">previous article in the series</a>, you read some general reflections on a pipes-and-filters architecture. This article gives an example in C#. </p> <p> The code shown here is from the <em>rx</em> branch of the example code Git repository. As the name implies, it uses <a href="https://reactivex.io/">ReactiveX</a>, also known as <a href="https://github.com/dotnet/reactive">Reactive Extensions for .NET</a>. </p> <p> To be honest, when refactoring an algorithm that's originally based on sequences of data, there's nothing particularly <em>reactive</em> going on here. You may, therefore, argue that it's a poor fit for this kind of architecture. Be that as it may, keep in mind that the example code <a href="https://x.com/Tyrrrz/status/1493369905869213700">in reality runs for about ten minutes</a>, so moving towards an architecture that supports progress reporting or cancellation may not be entirely inappropriate. </p> <p> The advantage of using Reactive Extensions for this particular example is that, compared to full message-bus-based frameworks, it offers a lightweight <a href="/2024/05/13/gratification">developer experience</a>, which enables us to focus on the essentials of the architecture. </p> <h3 id="e6e596b582524953aeab05c97f898d83"> Handle own scrobbles <a href="#e6e596b582524953aeab05c97f898d83">#</a> </h3> <p> We'll start with the part of the process that finds the user's own top scrobbles. Please consult with <a href="https://tyrrrz.me/">Oleksii Holub</a>'s <a href="https://tyrrrz.me/blog/pure-impure-segregation-principle#interleaved-impurities">original article</a>, or my article on <a href="/2025/04/10/characterising-song-recommendations">characterizing the implementation</a>, if you need a refresher. </p> <p> When using Reactive Extensions, should we model this part as <a href="https://learn.microsoft.com/dotnet/api/system.iobservable-1">IObservable&lt;T&gt;</a> or <a href="https://learn.microsoft.com/dotnet/api/system.iobserver-1">IObserver&lt;T&gt;</a>? </p> <p> Once you recall that <code>IObservable&lt;T&gt;</code>, <a href="/2025/03/03/reactive-monad">being a monad</a>, is eminently composable, the choice is clear. The <code>IObserver&lt;T&gt;</code> interface, on the other hand, gives rise to a <a href="/2021/09/02/contravariant-functors">contravariant functor</a>, but since that abstraction has weaker language support, we should go with the <a href="/2022/03/28/monads">monad</a>. </p> <p> We can start by declaring the type and its initializer: </p> <p> <pre><span style="color:blue;">public</span>&nbsp;<span style="color:blue;">sealed</span>&nbsp;<span style="color:blue;">class</span>&nbsp;<span style="color:#2b91af;">HandleOwnScrobblesObservable</span>&nbsp;:&nbsp;<span style="color:#2b91af;">IObservable</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt; { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:blue;">readonly</span>&nbsp;<span style="color:blue;">string</span>&nbsp;userName; &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:blue;">readonly</span>&nbsp;<span style="color:#2b91af;">SongService</span>&nbsp;_songService; &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">public</span>&nbsp;<span style="color:#2b91af;">HandleOwnScrobblesObservable</span>(<span style="color:blue;">string</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>,&nbsp;<span style="color:#2b91af;">SongService</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>) &nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">this</span>.userName&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_songService&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>; &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Implementation&nbsp;goes&nbsp;here...</span></pre> </p> <p> Given a <code>userName</code> we want to produce a (finite) stream of scrobbles, so we declare that the class implements <code><span style="color:#2b91af;">IObservable</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;</code>. An instance of this class is also going to need the <code>songService</code>, so that its implementation can query the out-of-process system that holds the data. </p> <p> What's that, you say? Why does <code>_songService</code> have a leading underscore, while the <code>userName</code> field does not? Because Oleksii Golub used that naming convention for that service, but <a href="/2021/05/17/against-consistency">I don't feel obliged to stay consistent</a> with that. </p> <p> Given that we already have working code, the implementation is relatively straightforward. </p> <p> <pre><span style="color:blue;">public</span>&nbsp;<span style="color:#2b91af;">IDisposable</span>&nbsp;<span style="font-weight:bold;color:#74531f;">Subscribe</span>(<span style="color:#2b91af;">IObserver</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">observer</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;<span style="color:#2b91af;">Observable</span>.<span style="color:#74531f;">Create</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;(<span style="font-weight:bold;color:#74531f;">Produce</span>).<span style="font-weight:bold;color:#74531f;">Subscribe</span>(<span style="font-weight:bold;color:#1f377f;">observer</span>); } <span style="color:blue;">private</span>&nbsp;<span style="color:blue;">async</span>&nbsp;<span style="color:#2b91af;">Task</span>&nbsp;<span style="font-weight:bold;color:#74531f;">Produce</span>(<span style="color:#2b91af;">IObserver</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;_songService.<span style="font-weight:bold;color:#74531f;">GetTopScrobblesAsync</span>(userName); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Pure</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobblesSnapshot</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">OrderByDescending</span>(<span style="font-weight:bold;color:#1f377f;">s</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">s</span>.ScrobbleCount) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Take</span>(100); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">foreach</span>&nbsp;(<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobble</span>&nbsp;<span style="font-weight:bold;color:#8f08c4;">in</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobblesSnapshot</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>.<span style="font-weight:bold;color:#74531f;">OnNext</span>(<span style="font-weight:bold;color:#1f377f;">scrobble</span>); &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>.<span style="font-weight:bold;color:#74531f;">OnCompleted</span>(); }</pre> </p> <p> If you are unused to Reactive Extensions, the hardest part may be figuring out how to implement <code>Subscribe</code> without getting bogged down with having to write too much of the implementation. Rx is, after all, a reusable library, so it should come with some building blocks for that, and it does. </p> <p> It seems that the simplest way to implement <code>Subscribe</code> is to delegate to <code>Observable.Create</code>, which takes an expression as input. You can write the implementation inline as a lambda expression, but here I've used a <code>private</code> helper method to slightly decouple the implementation from the library requirements. </p> <p> The first impure step is the same as we've already seen in the 'reference implementation', and the pure step should be familiar too. In the final impure step, the <code>Produce</code> method publishes the scrobbles to any subscribers that may be listening. </p> <p> This is the step where you'll need to extrapolate if you want to implement this kind of architecture on another framework than Reactive Extensions. If you're using a distributed message-based framework, you may have a message bus on which you publish messages, instead of <code>obs</code>. So <code>obs.OnNext</code> may, instead, be <code>bus.Publish</code>, or something to that effect. You may also need to package each scrobble in an explicit message object, and add correlation identifier and such. </p> <p> In many message-based frameworks (<a href="https://particular.net/nservicebus">NServiceBus</a>, for example), you're expected to implement some kind of message handler where messages arrive at a <code>Handle</code> method, typically on a stateless, long-lived object. This enables you to set up robust, distributed systems, but also comes with some overhead that requires you to coordinate or correlate messages. </p> <p> In this code example, <code>userName</code> is just a class field, and once the object is done producing messages, it signals so with <code>obs.OnCompleted()</code>, after which the stream has ended. </p> <p> The Rx implementation is simpler than some message-based systems I just outlined. That's the reason I chose it for this article. It doesn't mean that it's better, because that simplicity comes at the expense of missing capabilities. This system has no persistence, and I while I'm no expert in this field, I don't think it easily expands to a distributed system. And again, to perhaps belabour the obvious, I'm not insisting that any of those capabilities are always needed. I'm only trying to outline some of the trade-offs you should be aware of. </p> <h3 id="67a381ed508a482cb6359fb3eb9a2a88"> Handle other listeners <a href="#67a381ed508a482cb6359fb3eb9a2a88">#</a> </h3> <p> <code>HandleOwnScrobblesObservable</code> objects publish <code>Scrobble</code> objects. What does the next 'filter' look like? It's another observable stream, implemented by a class called <code>HandleOtherListenersObservable</code>. It implements <code><span style="color:#2b91af;">IObservable</span>&lt;<span style="color:#2b91af;">User</span>&gt;</code>, and its class declaration, constructor, and <code>Subscribe</code> implementation look a lot like what's already on display above. The main difference is the <code>Produce</code> method. </p> <p> <pre><span style="color:blue;">private</span>&nbsp;<span style="color:blue;">async</span>&nbsp;<span style="color:#2b91af;">Task</span>&nbsp;<span style="font-weight:bold;color:#74531f;">Produce</span>(<span style="color:#2b91af;">IObserver</span>&lt;<span style="color:#2b91af;">User</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListeners</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;_songService &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">GetTopListenersAsync</span>(scrobble.Song.Id); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Pure</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListenersSnapshot</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListeners</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Where</span>(<span style="font-weight:bold;color:#1f377f;">u</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">u</span>.TotalScrobbleCount&nbsp;&gt;=&nbsp;10_000) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">OrderByDescending</span>(<span style="font-weight:bold;color:#1f377f;">u</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">u</span>.TotalScrobbleCount) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Take</span>(20); &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;Impure</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">foreach</span>&nbsp;(<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListener</span>&nbsp;<span style="font-weight:bold;color:#8f08c4;">in</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">otherListenersSnapshot</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>.<span style="font-weight:bold;color:#74531f;">OnNext</span>(<span style="font-weight:bold;color:#1f377f;">otherListener</span>); &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>.<span style="font-weight:bold;color:#74531f;">OnCompleted</span>(); }</pre> </p> <p> Compared to the reference architecture, this implementation is hardly surprising. The most important point to make is that, as was the goal all along, this is another small <a href="/2025/01/13/recawr-sandwich">Recawr Sandwich</a>. </p> <p> A third observable, <code>HandleOtherScrobblesObservable</code>, handles the third step of the algorithm, and looks much like <code>HandleOtherListenersObservable</code>. You can see it in the Git repository. </p> <h3 id="267208bf1d944a2890cba3efcf3edd97"> Composition <a href="#267208bf1d944a2890cba3efcf3edd97">#</a> </h3> <p> The three observable streams constitute most of the building blocks required to implement the song recommendations algorithm. Notice the 'fan-out' nature of the observables. The first step starts with a single <code>userName</code> and produces up to 100 scrobbles. To handle <em>each</em> scrobble, a new instance of <code>HandleOtherListenersObservable</code> is required, and each of those produces up to twenty <code>User</code> notifications, and so on. </p> <p> In the abstract, we may view the <code>HandleOwnScrobblesObservable</code> constructor as a function from <code>string</code> to <code><span style="color:#2b91af;">IObservable</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;</code>. Likewise, we may view the <code>HandleOtherListenersObservable</code> constructor as a function that takes a single <code>Scrobble</code> as input, and gives an <code><span style="color:#2b91af;">IObservable</span>&lt;<span style="color:#2b91af;">User</span>&gt;</code> as return value. And finally, <code>HandleOtherScrobblesObservable</code> takes a single <code>User</code> as input to return <code><span style="color:#2b91af;">IObservable</span>&lt;<span style="color:#2b91af;">Song</span>&gt;</code> as output. </p> <p> Quick, what does that look like, and how do you compose them? </p> <p> Indeed, those are <a href="/2022/04/04/kleisli-composition">Kleisli arrows</a>, but in practice, we use <a href="/2022/03/28/monads">monadic bind</a> to compose them. In C# this usually means <code>SelectMany</code>. </p> <p> <pre><span style="color:blue;">public</span>&nbsp;<span style="color:blue;">async</span>&nbsp;<span style="color:#2b91af;">Task</span>&lt;<span style="color:#2b91af;">IReadOnlyList</span>&lt;<span style="color:#2b91af;">Song</span>&gt;&gt;&nbsp;<span style="font-weight:bold;color:#74531f;">GetRecommendationsAsync</span>(<span style="color:blue;">string</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songs</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">HandleOwnScrobblesObservable</span>(<span style="font-weight:bold;color:#1f377f;">userName</span>,&nbsp;_songService) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">SelectMany</span>(<span style="font-weight:bold;color:#1f377f;">s</span>&nbsp;=&gt;&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">HandleOtherListenersObservable</span>(<span style="font-weight:bold;color:#1f377f;">s</span>,&nbsp;_songService)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">SelectMany</span>(<span style="font-weight:bold;color:#1f377f;">u</span>&nbsp;=&gt;&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">HandleOtherScrobblesObservable</span>(<span style="font-weight:bold;color:#1f377f;">u</span>,&nbsp;_songService)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">ToList</span>(); &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songs</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">OrderByDescending</span>(<span style="font-weight:bold;color:#1f377f;">s</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">s</span>.Rating) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Take</span>(200) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">ToArray</span>(); }</pre> </p> <p> The fourth step of the algorithm, you may notice, isn't implemented as an observable, but rather as a standard LINQ pipeline. This is because sorting is required, and if there's a way to sort an observable, I'm not aware of it. After all, given that observable objects may represent infinite data streams, I readily accept that there's no <code>OrderByDescending</code> method on <code>IObservable&lt;T&gt;</code>. (But then, the <a href="https://www.nuget.org/packages/System.Reactive/">System.Reactive</a> library defines <code>Min</code> and <code>Max</code> operations, and exactly how those work when faced with infinite streams, I haven't investigated.) </p> <p> While I could have created a helper function for that small <code>OrderByDescending</code>/<code>Take</code>/<code>ToArray</code> pipeline, I consider it under the <a href="https://wiki.haskell.org/Fairbairn_threshold">Fairbairn threshold</a>. </p> <h3 id="d8778bbf0f6a4e14b0d24181849ff943"> Query syntax <a href="#d8778bbf0f6a4e14b0d24181849ff943">#</a> </h3> <p> You can also compose the algorithm using query syntax, which I personally find prettier. </p> <p> <pre><span style="color:#2b91af;">IObservable</span>&lt;<span style="color:#2b91af;">Song</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">from</span>&nbsp;scr&nbsp;<span style="color:blue;">in</span>&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">HandleOwnScrobblesObservable</span>(<span style="font-weight:bold;color:#1f377f;">userName</span>,&nbsp;_songService) &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">from</span>&nbsp;usr&nbsp;<span style="color:blue;">in</span>&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">HandleOtherListenersObservable</span>(scr,&nbsp;_songService) &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">from</span>&nbsp;sng&nbsp;<span style="color:blue;">in</span>&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">HandleOtherScrobblesObservable</span>(usr,&nbsp;_songService) &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">select</span>&nbsp;sng; <span style="color:#2b91af;">IList</span>&lt;<span style="color:#2b91af;">Song</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">songs</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">obs</span>.<span style="font-weight:bold;color:#74531f;">ToList</span>(); <span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">songs</span> &nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">OrderByDescending</span>(<span style="font-weight:bold;color:#1f377f;">s</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">s</span>.Rating) &nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Take</span>(200) &nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">ToArray</span>();</pre> </p> <p> In this code snippet I've used explicit variable type declarations (instead of using the <code>var</code> keyword) for the sole purpose of making it easier to see which types are involved. </p> <h3 id="5b5ac82d3d67486bac8118e59f6b9d1b"> Conclusion <a href="#5b5ac82d3d67486bac8118e59f6b9d1b">#</a> </h3> <p> This article shows an implementation example of refactoring the song recommendations problem to a pipes-and-filters architecture. It uses Reactive Extensions for .NET, since this showcases the (de)composition in the simplest possible way. Hopefully, you can extrapolate from this to a more elaborate, distributed asynchronous message-based system, if you need something like that. </p> <p> The next example makes a small move in that direction. </p> <p> <strong>Next:</strong> <a href="/2025/07/28/song-recommendations-with-f-agents">Song recommendations with F# agents</a>. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/07/21/song-recommendations-with-c-reactive-extensions Song recommendations with pipes and filters https://blog.ploeh.dk/2025/07/14/song-recommendations-with-pipes-and-filters/ Mon, 14 Jul 2025 14:52:00 UTC <div id="post"> <p> <em>Composing small Recawr Sandwiches.</em> </p> <p> This article is part of a larger series that outlines various <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">alternative ways to design with functional programming</a>. That (first) article contains a table of contents, as well as outlines the overall programme and the running example. In short, the example is a song recommendation engine that works on large data sets. </p> <p> Previous articles in this series have explored <a href="/2025/04/28/song-recommendations-as-an-impureim-sandwich">refactoring to a pure function</a> and <a href="/2025/06/09/song-recommendations-from-combinators">composing impure actions with combinators</a>. In the next few articles, we'll look at how to use message-based architectures to decouple the algorithm into smaller <a href="/2025/01/13/recawr-sandwich">Recawr Sandwiches</a>. As an overall concept, we may term such an architecture <em>pipes and filters</em>. The book <a href="/ref/eip">Enterprise Integration Patterns</a> is a great resource for this kind of (de)composition. Don't be mislead by the title: In essence, it's neither about enterprise programming nor integration. </p> <h3 id="0ce73de5ddf24106adc6a3890fc3fc9b"> Workflows of small sandwiches <a href="#0ce73de5ddf24106adc6a3890fc3fc9b">#</a> </h3> <p> As speculated in <a href="/2025/04/28/song-recommendations-as-an-impureim-sandwich">Song recommendations as an Impureim Sandwich</a>, the data sets involved when finding song recommendations for a user may be so large that an <a href="/2020/03/02/impureim-sandwich">Impureim Sandwich</a> is impractical. </p> <p> On the other hand, a message-based system essentially consists of many small message handlers that each may be implemented as Impureim Sandwiches. I've already briefly discussed this kind of decomposition in <a href="/2017/07/10/pure-interactions">Pure interactions</a>, where each message handler or 'actor' is a small process equivalent to a web request handler (Controller) or similar. </p> <p> When it comes to the song recommendation problem, we may consider a small pipes-and-filters architecture that uses small 'filters' to start even more work, handled by other filters, and so on. </p> <p> <img src="/content/binary/song-recommendations-pipes-and-filters.png" alt="Three boxes with arrows between them indicating messages going from left to right."> </p> <p> Depending on the exact implementation details, you may call this <em>pipes and filters</em>, <em>reactive functional programming</em>, the <em>actor model</em>, <em>map/reduce</em>, or something else. What I consider crucial in this context is that each 'filter' is a small Recawr Sandwich. It queries the out-of-process music data service, applies a <a href="https://en.wikipedia.org/wiki/Pure_function">pure function</a> to that data, and finally sends more messages over some impure channel. In the following articles, you'll see code examples. </p> <ul> <li><a href="/2025/07/21/song-recommendations-with-c-reactive-extensions">Song recommendations with C# Reactive Extensions</a></li> <li><a href="/2025/07/28/song-recommendations-with-f-agents">Song recommendations with F# agents</a></li> </ul> <p> As I'm writing this, I don't plan to supply a <a href="https://www.haskell.org/">Haskell</a> example. The main reason is that I've no experience with writing asynchronous message-based systems with Haskell, and while a quick web search indicates that there are plenty of libraries for <a href="https://wiki.haskell.org/Functional_Reactive_Programming">reactive functional programming</a>, on the other hand, I can't find much when it comes to message-bus-based asynchronous messaging. </p> <p> Perhaps it'd be more idiomatic to supply an <a href="https://www.erlang.org/">Erlang</a> example, but it's been too many years since I tried teaching myself Erlang, and I never became proficient with it. </p> <h3 id="02e1dda85220460db2d3f568c7c98942"> Modelling <a href="#02e1dda85220460db2d3f568c7c98942">#</a> </h3> <p> It turns out that when you decompose the song recommendation problem into smaller 'filters', each of which is a Recawr Sandwich, you arrive at a decomposition that looks suspiciously close to what we saw in <a href="/2025/06/09/song-recommendations-from-combinators">Song recommendations from combinators</a>. And as <a href="https://tyrrrz.me/blog/pure-impure-segregation-principle">Oleksii Holub wrote</a>, </p> <blockquote> <p> "However, instead of having one cohesive element to reason about, we ended up with multiple fragments, each having no meaning or value of their own. While unit testing of individual parts may have become easier, the benefit is very questionable, as it provides no confidence in the correctness of the algorithm as a whole." </p> </blockquote> <p> This remains true here. Why even bother, then? </p> <p> The purpose of this article series is to present alternatives, just like <a href="https://fsharpforfunandprofit.com/">Scott Wlaschin</a>'s excellent article <a href="https://fsharpforfunandprofit.com/posts/13-ways-of-looking-at-a-turtle/">Thirteen ways of looking at a turtle</a>. It may be that a given design alternative isn't the absolute best fit for the song recommendation problem, but it may, on the other hand, be a good fit for some other problem that you run into. If so, you should be able to extrapolate from the articles in this series. </p> <h3 id="577f32d80d674ac48c254e231f647d26"> Conclusion <a href="#577f32d80d674ac48c254e231f647d26">#</a> </h3> <p> Given that <a href="https://x.com/Tyrrrz/status/1493369905869213700">we know that the real code that the song recommendation example is based off runs for about ten minutes</a>, some kind of asynchronous process that may support progress indication or cancellation may be worth considering. This easily leads us in the direction of some kind of pipes-and-filters architecture. </p> <p> You can implement such an architecture with in-memory message streams, as the next article does, or you can go with a full-fledged messaging system based on persistent message buses and distributed, restartable message handlers. The details vary, but it's essentially the same kind of architecture. </p> <p> <strong>Next:</strong> <a href="/2025/07/21/song-recommendations-with-c-reactive-extensions">Song recommendations with C# Reactive Extensions</a>. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/07/14/song-recommendations-with-pipes-and-filters A parser and interpreter for a very small language https://blog.ploeh.dk/2025/07/07/a-parser-and-interpreter-for-a-very-small-language/ Mon, 07 Jul 2025 06:39:00 UTC <div id="post"> <p> <em>A single Haskell script file.</em> </p> <p> I recently took the final exam in a course on programming language design. One of the questions was about a tiny language, and since this was a take-home exam running over many days, I had time to spare. Although it wasn't part of any questions, I decided to write an interpreter to back up some claims I made in my answers. </p> <p> This article documents my prototype parser and interpreter. </p> <h3 id="f2981bf1bd2e4eba8596864f9bc75777"> Language description <a href="#f2981bf1bd2e4eba8596864f9bc75777">#</a> </h3> <p> To be clear, the exam question was <em>not</em> to implement an interpreter, but rather some questions about attributes of the language. The description here is reprinted with kind permission from <a href="https://di.ku.dk/english/staff/?pure=en/persons/162114">Torben Ægidius Mogensen</a>. </p> <blockquote> <p> Consider a functional language where values can be Booleans and pairs. A syntax for the language is given below: </p> <table style="border: none; margin-left: auto; margin-right: auto;"> <tr style="border: none;"> <td style="border: none;"><em>Program</em></td> <td style="border: none;">&rarr;</td> <td style="border: none;"><em>Function<sup>+</sup></em></td> </tr> <tr style="border: none;"> <td style="border: none;"><em>Function</em></td> <td style="border: none;">&rarr;</td> <td style="border: none;"><strong>Fid</strong> <em>Pattern<sup>+</sup></em> = <em>Exp</em></td> </tr> <tr style="border: none;"> <td style="border: none;"><em>Pattern</em></td> <td style="border: none;">&rarr;</td> <td style="border: none;"><strong>Vid</strong> | <code>true</code> | <code>false</code> | (<em>Pattern</em>, <em>Pattern</em>)</td> </tr> <tr style="border: none;"> <td style="border: none;"><em>Exp</em></td> <td style="border: none;">&rarr;</td> <td style="border: none;"><strong>Vid</strong> | <code>true</code> | <code>false</code> | <strong>Fid</strong> <em>Exp<sup>+</sup></em> | (<em>Exp</em>)</td> </tr> </table> <p> where <strong>Fid</strong> denotes function identifiers (which are lower case) and <strong>Vid</strong> denotes variable identifiers (which are upper case). There can be multiple rules for each functions, but rules must have disjoint patterns. All function calls must be fully applied (no partial applications, so no higher-order functions). A program is executed by calling any function with any argument constructed by pairs and Booleans. An example program is </p> <p> <pre>and true X = X and false X = false alltrue true = true alltrue false = false alltrue (X, Y) = and (alltrue X) (alltrue Y)</pre> </p> <p> Calling <code>alltrue (true, (false, true))</code> will return <code>false</code>, but <code>alltrue ((true, true), (true, true))</code> will return <code>true</code>. </p> </blockquote> <p> The exam goes on to ask some questions about termination as a property of the language, and whether or not it's <a href="https://en.wikipedia.org/wiki/Turing_completeness">Turing complete</a>, but that's not the scope of this article. Rather, I'd like to describe a prototype parser and interpreter I wrote as a single throwaway <a href="/2024/02/05/statically-and-dynamically-typed-scripts">script file in Haskell</a>. </p> <h3 id="11c8dce522cf48f882e56aa8182bd268"> Declarations and imports <a href="#11c8dce522cf48f882e56aa8182bd268">#</a> </h3> <p> The code is a single <a href="https://www.haskell.org/">Haskell</a> module that I interacted with via GHCi (the <a href="https://en.wikipedia.org/wiki/Glasgow_Haskell_Compiler">GHC</a> <a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop">REPL</a>). It starts with a single pragma, a module declaration, and imports. </p> <p> <pre>{-#&nbsp;<span style="color:gray;">LANGUAGE</span>&nbsp;FlexibleContexts&nbsp;#-} <span style="color:blue;">module</span>&nbsp;Bopa&nbsp;<span style="color:blue;">where</span> <span style="color:blue;">import</span>&nbsp;Control.Monad.Identity&nbsp;(<span style="color:blue;">Identity</span>) <span style="color:blue;">import</span>&nbsp;Data.Bifunctor&nbsp;(<span style="color:#2b91af;">first</span>) <span style="color:blue;">import</span>&nbsp;Data.Foldable&nbsp;(<span style="color:#2b91af;">find</span>) <span style="color:blue;">import</span>&nbsp;Data.List.NonEmpty&nbsp;(<span style="color:blue;">NonEmpty</span>((:|))) <span style="color:blue;">import</span>&nbsp;<span style="color:blue;">qualified</span>&nbsp;Data.List.NonEmpty&nbsp;<span style="color:blue;">as</span>&nbsp;NE <span style="color:blue;">import</span>&nbsp;Data.Set&nbsp;(<span style="color:blue;">Set</span>) <span style="color:blue;">import</span>&nbsp;<span style="color:blue;">qualified</span>&nbsp;Data.Set&nbsp;<span style="color:blue;">as</span>&nbsp;Set <span style="color:blue;">import</span>&nbsp;Text.Parsec</pre> </p> <p> The <em>parsec</em> API requires the <code>FlexibleContexts</code> language pragma. The name, <em>Bopa</em> I simply derived from <strong>Bo</strong>ols and <strong>Pa</strong>irs, although I'm aware that this little combination of letters has quite an <a href="https://en.wikipedia.org/wiki/BOPA">alternative connotation for many Danes</a>, including myself. </p> <p> Apart from the <code>base</code> library, the packages <a href="https://hackage.haskell.org/package/parsec">parsec</a> and <a href="https://hackage.haskell.org/package/containers">containers</a> are required. I didn't use an explicit build system, but if they're not already present on your system, <a href="https://stackoverflow.com/a/39848577/126014">you can ask GHCi to load them</a>. </p> <h3 id="6813c3c9813240c1ab806d5126f2a6ce"> AST <a href="#6813c3c9813240c1ab806d5126f2a6ce">#</a> </h3> <p> The above language description is a <a href="https://en.wikipedia.org/wiki/Context-free_grammar">context-free grammar</a>, which translates easily into Haskell type declarations. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;Program&nbsp;=&nbsp;NonEmpty&nbsp;Function <span style="color:blue;">data</span>&nbsp;Function&nbsp;= &nbsp;&nbsp;Function&nbsp;{&nbsp;fid&nbsp;::&nbsp;String,&nbsp;fpats&nbsp;::&nbsp;NonEmpty&nbsp;Pattern,&nbsp;fbody&nbsp;::&nbsp;Exp&nbsp;} &nbsp;&nbsp;<span style="color:blue;">deriving</span>&nbsp;(<span style="color:#2b91af;">Eq</span>,&nbsp;<span style="color:#2b91af;">Show</span>) <span style="color:blue;">data</span>&nbsp;Pattern&nbsp;= &nbsp;&nbsp;VarPat&nbsp;String&nbsp;|&nbsp;TPat&nbsp;|&nbsp;FPat&nbsp;|&nbsp;PairPat&nbsp;Pattern&nbsp;Pattern&nbsp;<span style="color:blue;">deriving</span>&nbsp;(<span style="color:#2b91af;">Eq</span>,&nbsp;<span style="color:#2b91af;">Show</span>) <span style="color:blue;">data</span>&nbsp;Exp&nbsp;= &nbsp;&nbsp;VarExp&nbsp;String&nbsp;|&nbsp;TExp&nbsp;|&nbsp;FExp&nbsp;|&nbsp;CallExp&nbsp;String&nbsp;(NonEmpty&nbsp;Exp) &nbsp;&nbsp;<span style="color:blue;">deriving</span>&nbsp;(<span style="color:#2b91af;">Eq</span>,&nbsp;<span style="color:#2b91af;">Show</span>)</pre> </p> <p> The description doesn't explicitly state how to interpret the superscript +, but I've interpreted it as meaning <em>one or more</em>. Therefore, a <code>Program</code> is a <a href="https://hackage.haskell.org/package/base/docs/Data-List-NonEmpty.html">NonEmpty</a> list of <code>Function</code> values. The same line of reasoning applies for the other places where the + sign appears. </p> <p> Notice that there's more than one representation of Boolean values; <code>TPat</code> is the <code>true</code> <em>pattern</em>, while <code>TExp</code> is the <code>true</code> <em>expression</em>, and likewise for <code>false</code>. </p> <p> These types describe the entire language, and you can, in principle, create programs directly using this API. While I didn't do that (because I wrote a parser instead), here's what the above <code>and</code> function looks like as an <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">abstract syntax tree</a> (AST): </p> <p> <pre>Function "and" (TPat :| [VarPat "X"]) (VarExp "X") :| [Function "and" (FPat :| [VarPat "X"]) FExp]</pre> </p> <p> Recall that the <code>:|</code> operator is the <code>NonEmpty</code> data constructor. </p> <h3 id="16d45a1b8baf432dbc4922b746e4689a"> Source code parsers <a href="#16d45a1b8baf432dbc4922b746e4689a">#</a> </h3> <p> I wanted to be able to write programs directly in the Bopa language, and not just as ASTs, so the next step was writing parsers for each of the data types defined above. As strongly implied by the above imports, I used the <em>parsec</em> package for that. </p> <p> The <code>Program</code> type is only an alias, and once I have a parser for <code>Function</code>, that one should be straightforward. The parser of <code>Function</code> values is, however, more involved. </p> <p> <pre><span style="color:#2b91af;">functionParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Function</span> functionParser&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;fnid&nbsp;&lt;-&nbsp;many1&nbsp;lower &nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;pp&nbsp;=&nbsp;many1&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span>)&nbsp;&gt;&gt;&nbsp;patternParser &nbsp;&nbsp;<span style="color:green;">--&nbsp;Next&nbsp;line&nbsp;based&nbsp;on&nbsp;https://stackoverflow.com/a/65570028/126014 </span>&nbsp;&nbsp;patterns&nbsp;&lt;-&nbsp;<span style="color:#2b91af;">(:|)</span>&nbsp;&lt;$&gt;&nbsp;pp&nbsp;&lt;*&gt;&nbsp;pp&nbsp;`manyTill`&nbsp;try&nbsp;(many1&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span>)&nbsp;&gt;&gt;&nbsp;char&nbsp;<span style="color:#a31515;">&#39;=&#39;</span>) &nbsp;&nbsp;skipMany1&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span>) &nbsp;&nbsp;Function&nbsp;fnid&nbsp;patterns&nbsp;&lt;$&gt;&nbsp;expParser</pre> </p> <p> I readily admit that I don't have much experience with <em>parsec</em>, so it's possible that this could be done more elegantly. As the comment indicates, I struggled somewhat with a detail or two. I had trouble making it consume patterns until it meets the <code>'='</code> character. </p> <p> The <code>functionParser</code> depends on another parser named <code>pairPatParser</code>, which again is composed from smaller parsers that handle each case of the <code>Pattern</code> <a href="https://en.wikipedia.org/wiki/Tagged_union">sum type</a>. </p> <p> <pre><span style="color:#2b91af;">varPatParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Pattern</span> varPatParser&nbsp;=&nbsp;VarPat&nbsp;&lt;$&gt;&nbsp;many1&nbsp;upper <span style="color:#2b91af;">tPatParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Pattern</span> tPatParser&nbsp;=&nbsp;string&nbsp;<span style="color:#a31515;">&quot;true&quot;</span>&nbsp;&gt;&gt;&nbsp;<span style="color:blue;">return</span>&nbsp;TPat <span style="color:#2b91af;">fPatParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Pattern</span> fPatParser&nbsp;=&nbsp;string&nbsp;<span style="color:#a31515;">&quot;false&quot;</span>&nbsp;&gt;&gt;&nbsp;<span style="color:blue;">return</span>&nbsp;FPat <span style="color:#2b91af;">pairPatParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Pattern</span> pairPatParser&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;char&nbsp;<span style="color:#a31515;">&#39;(&#39;</span> &nbsp;&nbsp;p1&nbsp;&lt;-&nbsp;patternParser &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;char&nbsp;<span style="color:#a31515;">&#39;,&#39;</span> &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;skipMany&nbsp;$&nbsp;char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span> &nbsp;&nbsp;p2&nbsp;&lt;-&nbsp;patternParser &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;char&nbsp;<span style="color:#a31515;">&#39;)&#39;</span> &nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;PairPat&nbsp;p1&nbsp;p2 <span style="color:#2b91af;">patternParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Pattern</span> patternParser&nbsp;= &nbsp;&nbsp;try&nbsp;varPatParser&nbsp;&lt;|&gt;&nbsp;try&nbsp;tPatParser&nbsp;&lt;|&gt;&nbsp;try&nbsp;fPatParser&nbsp;&lt;|&gt;&nbsp;try&nbsp;pairPatParser</pre> </p> <p> You might argue that the first three are so simple that they may not really qualify for the status of top-level values, but being a <em>parsec</em> newbie, I found that it helped <em>me</em> to structure the code that way. The only one of those values more complicated than a one-liner is, obviously, <code>pairPatParser</code>. I later discovered <a href="https://hackage.haskell.org/package/parsec/docs/Text-Parsec.html#v:between">between</a> and <a href="https://hackage.haskell.org/package/parsec/docs/Text-Parsec.html#v:sepBy1">sepBy1</a>, so it's possible I could also have defined <code>pairPatParser</code> as a composition of such combinators. I didn't, however, try, since this is, after all, throwaway prototype code, and what's there already works as intended. </p> <p> As an aside, I would usually keenly attempt such refactorings, but I was working without automated tests. Yes, shocking, I know, but setting up unit tests for Haskell is, unfortunately, a bit of a hassle, and given the nature of the work, I considered <a href="/2018/11/12/what-to-test-and-not-to-test">doing without tests a reasonable trade-off</a>. </p> <p> This takes care of parsing <code>Pattern</code> values, but notice that <code>functionParser</code> also depends on <code>expParser</code>, which, not surprisingly, parses <code>Exp</code> values. Like <code>patternParser</code> it does that by defining a helper parser for each sum type case, and then combining them into one larger parser. </p> <p> <pre><span style="color:#2b91af;">varExpParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Exp</span> varExpParser&nbsp;=&nbsp;VarExp&nbsp;&lt;$&gt;&nbsp;many1&nbsp;upper <span style="color:#2b91af;">tExpParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Exp</span> tExpParser&nbsp;=&nbsp;string&nbsp;<span style="color:#a31515;">&quot;true&quot;</span>&nbsp;&gt;&gt;&nbsp;<span style="color:blue;">return</span>&nbsp;TExp <span style="color:#2b91af;">fExpParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Exp</span> fExpParser&nbsp;=&nbsp;string&nbsp;<span style="color:#a31515;">&quot;false&quot;</span>&nbsp;&gt;&gt;&nbsp;<span style="color:blue;">return</span>&nbsp;FExp <span style="color:#2b91af;">callExpParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Exp</span> callExpParser&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;fnid&nbsp;&lt;-&nbsp;many1&nbsp;lower &nbsp;&nbsp;skipMany1&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span>) &nbsp;&nbsp;exps&nbsp;&lt;-&nbsp;NE.fromList&nbsp;&lt;$&gt;&nbsp;expParser&nbsp;`sepBy1`&nbsp;many1&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span>) &nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;CallExp&nbsp;fnid&nbsp;exps <span style="color:#2b91af;">expParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Exp</span> expParser&nbsp;= &nbsp;&nbsp;try&nbsp;varExpParser&nbsp;&lt;|&gt; &nbsp;&nbsp;try&nbsp;tExpParser&nbsp;&lt;|&gt; &nbsp;&nbsp;try&nbsp;fExpParser&nbsp;&lt;|&gt; &nbsp;&nbsp;try&nbsp;callExpParser&nbsp;&lt;|&gt; &nbsp;&nbsp;between&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;(&#39;</span>)&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;)&#39;</span>)&nbsp;expParser</pre> </p> <p> Even though I generally favoured implementing each sum type case in a separate, named parser, I inlined parsing of the parenthesized expression; partly because it's so simple, and partly because I didn't know what to call it. </p> <p> You can see that at this point, I'd discovered the <code>between</code> and <code>sepBy1</code> combinators. </p> <p> Finally, it's possible to compose all these smaller parsers together to a parser of Bopa programs. </p> <p> <pre><span style="color:#2b91af;">programParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;(<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Function</span>) programParser&nbsp;=&nbsp;NE.fromList&nbsp;&lt;$&gt;&nbsp;functionParser&nbsp;`sepEndBy1`&nbsp;many1&nbsp;endOfLine</pre> </p> <p> This, however, is parser. How do you run it? </p> <p> Here's a way: </p> <p> <pre><span style="color:#2b91af;">parseProgram</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;<span style="color:blue;">Identity</span>&nbsp;<span style="color:#2b91af;">Char</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;s&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Either</span>&nbsp;<span style="color:blue;">ParseError</span>&nbsp;(<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Function</span>) parseProgram&nbsp;=&nbsp;parse&nbsp;programParser&nbsp;<span style="color:#a31515;">&quot;&quot;</span></pre> </p> <p> You may, for example, try to parse the above <code>and</code> function: </p> <p> <pre>ghci&gt; parseProgram "and true X = X\nand false X = false" Right (Function {fid = "and", fpats = TPat :| [VarPat "X"], fbody = VarExp "X"} :| [Function {fid = "and", fpats = FPat :| [VarPat "X"], fbody = FExp}])</pre> </p> <p> (Output manually formatted to improve readability.) </p> <p> In practice, however, I didn't much do that. Instead, I created source code files and loaded them with the basic file-reading APIs included in the <code>base</code> package. You'll see examples of this later. </p> <h3 id="c513d63f70bb4cccb4f1363cb18fc046"> Arguments <a href="#c513d63f70bb4cccb4f1363cb18fc046">#</a> </h3> <p> As described, running a program requires construction of a Boolean value, or pairs of Boolean values, something the language itself does not allow. That's the reason I haven't yet modelled it. </p> <p> <pre><span style="color:blue;">data</span>&nbsp;Arg&nbsp;=&nbsp;TArg&nbsp;|&nbsp;FArg&nbsp;|&nbsp;PairArg&nbsp;Arg&nbsp;Arg&nbsp;<span style="color:blue;">deriving</span>&nbsp;(<span style="color:#2b91af;">Eq</span>,&nbsp;<span style="color:#2b91af;">Ord</span>,&nbsp;<span style="color:#2b91af;">Show</span>)</pre> </p> <p> Notice that <code>true</code> and <code>false</code> gets yet another representation as either <code>TArg</code> or <code>FArg</code>. </p> <p> If I want to be able to run programs by typing <code>alltrue (true, (false, true))</code>, instead of painstakingly creating ASTs, I need a parser for this data type as well. That's not going to be a source code parser, but rather part of a command-line parser. </p> <p> <pre><span style="color:#2b91af;">tArgParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Arg</span> tArgParser&nbsp;=&nbsp;string&nbsp;<span style="color:#a31515;">&quot;true&quot;</span>&nbsp;&gt;&gt;&nbsp;<span style="color:blue;">return</span>&nbsp;TArg <span style="color:#2b91af;">fArgParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Arg</span> fArgParser&nbsp;=&nbsp;string&nbsp;<span style="color:#a31515;">&quot;false&quot;</span>&nbsp;&gt;&gt;&nbsp;<span style="color:blue;">return</span>&nbsp;FArg <span style="color:#2b91af;">pairArgParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Arg</span> pairArgParser&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;char&nbsp;<span style="color:#a31515;">&#39;(&#39;</span> &nbsp;&nbsp;p1&nbsp;&lt;-&nbsp;argParser &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;char&nbsp;<span style="color:#a31515;">&#39;,&#39;</span> &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;skipMany&nbsp;$&nbsp;char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span> &nbsp;&nbsp;p2&nbsp;&lt;-&nbsp;argParser &nbsp;&nbsp;_&nbsp;&lt;-&nbsp;char&nbsp;<span style="color:#a31515;">&#39;)&#39;</span> &nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;PairArg&nbsp;p1&nbsp;p2 <span style="color:#2b91af;">argParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Arg</span> argParser&nbsp;=&nbsp;tArgParser&nbsp;&lt;|&gt;&nbsp;fArgParser&nbsp;&lt;|&gt;&nbsp;pairArgParser</pre> </p> <p> To be honest, I think that I just copied and pasted <code>pairPatParser</code> and changed a few things. It looks that way, doesn't it? </p> <h3 id="697f39dc3ff247478681ba5fbe174cfd"> Entry points <a href="#697f39dc3ff247478681ba5fbe174cfd">#</a> </h3> <p> In order to execute a program, you need more than arguments. You need to define which function to call. I decided that this was close enough to defining a program <a href="https://en.wikipedia.org/wiki/Entry_point">entry point</a> that it gave name to the next type. </p> <p> <pre><span style="color:blue;">data</span>&nbsp;Entry&nbsp;=&nbsp;Entry&nbsp;String&nbsp;(NonEmpty&nbsp;Arg)&nbsp;<span style="color:blue;">deriving</span>&nbsp;(<span style="color:#2b91af;">Eq</span>,&nbsp;<span style="color:#2b91af;">Show</span>)</pre> </p> <p> The <code>String</code> value identifies the desired function by name, and the <code>NonEmpty</code> list supplies the arguments. </p> <p> Since I wish to be able to run a program by writing e.g. <code>alltrue ((true, true), (true, true))</code>, I need a parser for that, too. </p> <p> <pre><span style="color:#2b91af;">entryParser</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;m&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">ParsecT</span>&nbsp;s&nbsp;()&nbsp;m&nbsp;<span style="color:blue;">Entry</span> entryParser&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;fnid&nbsp;&lt;-&nbsp;many1&nbsp;lower &nbsp;&nbsp;skipMany1&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span>) &nbsp;&nbsp;args&nbsp;&lt;-&nbsp;NE.fromList&nbsp;&lt;$&gt;&nbsp;argParser&nbsp;`sepBy1`&nbsp;many1&nbsp;(char&nbsp;<span style="color:#a31515;">&#39;&nbsp;&#39;</span>) &nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;Entry&nbsp;fnid&nbsp;args</pre> </p> <p> This, again, is a parser; it's convenient to also define a function to run it against input. </p> <p> <pre><span style="color:#2b91af;">parseEntry</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Stream</span>&nbsp;s&nbsp;<span style="color:blue;">Identity</span>&nbsp;<span style="color:#2b91af;">Char</span>&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;s&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Either</span>&nbsp;<span style="color:blue;">ParseError</span>&nbsp;<span style="color:blue;">Entry</span> parseEntry&nbsp;=&nbsp;parse&nbsp;entryParser&nbsp;<span style="color:#a31515;">&quot;&quot;</span></pre> </p> <p> Let's see if it works: </p> <p> <pre>ghci&gt; parseEntry "alltrue ((true, true), (true, true))" Right (Entry "alltrue" (PairArg (PairArg TArg TArg) (PairArg TArg TArg) :| []))</pre> </p> <p> That seems promising. </p> <h3 id="efcf351461f5483aa6f1ec9945fd3039"> Parameter binding <a href="#efcf351461f5483aa6f1ec9945fd3039">#</a> </h3> <p> Armed with the ability to parse programs as well as entry points, 'all' that remains is to execute the program. To that end, I wrote an interpreter. It works with a few helper functions, the first of which attempts to bind patterns to arguments. </p> <p> For example, if we have a variable-name pattern such as <code>X</code> and an argument such as <code>(true, false)</code>, we can bind <code>X</code> to that value. Some examples will help, but I'll show the function first, and then talk you through it. </p> <p> <pre><span style="color:green;">--&nbsp;Attempt&nbsp;pattern&nbsp;matching&nbsp;and,&nbsp;if&nbsp;possible,&nbsp;bind&nbsp;variables&nbsp;to&nbsp;arguments. --&nbsp;Returns&nbsp;an&nbsp;association&nbsp;list&nbsp;of&nbsp;bound&nbsp;variables&nbsp;(an&nbsp;&#39;environment&#39;),&nbsp;if&nbsp;any. --&nbsp;Returns&nbsp;Left&nbsp;with&nbsp;an&nbsp;error&nbsp;message&nbsp;if&nbsp;no&nbsp;match. </span><span style="color:#2b91af;">tryBind</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Pattern</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Arg</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Either</span>&nbsp;<span style="color:#2b91af;">String</span>&nbsp;[(<span style="color:#2b91af;">String</span>,&nbsp;<span style="color:blue;">Arg</span>)] tryBind&nbsp;(VarPat&nbsp;p&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;(arg&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;=&nbsp;Right&nbsp;[(p,&nbsp;arg)] tryBind&nbsp;(TPat&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;(TArg&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;=&nbsp;Right&nbsp;<span style="color:blue;">[]</span> tryBind&nbsp;(FPat&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;(FArg&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;=&nbsp;Right&nbsp;<span style="color:blue;">[]</span> tryBind&nbsp;(PairPat&nbsp;p1&nbsp;p2&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;((PairArg&nbsp;a1&nbsp;a2)&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;= &nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;b1&nbsp;=&nbsp;tryBind&nbsp;(NE.singleton&nbsp;p1)&nbsp;(NE.singleton&nbsp;a1) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b2&nbsp;=&nbsp;tryBind&nbsp;(NE.singleton&nbsp;p2)&nbsp;(NE.singleton&nbsp;a2) &nbsp;&nbsp;<span style="color:blue;">in</span>&nbsp;<span style="color:#2b91af;">(++)</span>&nbsp;&lt;$&gt;&nbsp;b1&nbsp;&lt;*&gt;&nbsp;b2 tryBind&nbsp;(pat&nbsp;:|&nbsp;(p:ps))&nbsp;(arg&nbsp;:|&nbsp;(a:as))&nbsp;= &nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;b&nbsp;&nbsp;=&nbsp;tryBind&nbsp;(NE.singleton&nbsp;pat)&nbsp;(NE.singleton&nbsp;arg) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bs&nbsp;=&nbsp;tryBind&nbsp;(p&nbsp;:|&nbsp;ps)&nbsp;(a&nbsp;:|&nbsp;as) &nbsp;&nbsp;<span style="color:blue;">in</span>&nbsp;<span style="color:#2b91af;">(++)</span>&nbsp;&lt;$&gt;&nbsp;b&nbsp;&lt;*&gt;&nbsp;bs tryBind&nbsp;_&nbsp;args&nbsp;=&nbsp;Left&nbsp;(<span style="color:#a31515;">&quot;Could&nbsp;not&nbsp;match&nbsp;&quot;</span>&nbsp;++&nbsp;<span style="color:blue;">show</span>&nbsp;args&nbsp;++&nbsp;<span style="color:#a31515;">&quot;.&quot;</span>)</pre> </p> <p> Notice the type declaration: The function takes a <code>NonEmpty</code> list of <code>Pattern</code> values, and another <code>NonEmpty</code> list of <code>Arg</code> values. The first precondition in order to achieve a successful result is that these two lists need to have the same length. If we have more arguments than patterns, we run out of patterns. If we have more patterns than arguments, we can't bind all the parameters in the patterns, and partial application is not allowed. </p> <p> The first four rules of the <code>tryBind</code> function attempt to match a <em>single</em> <code>Pattern</code> value to a <em>single</em> <code>Arg</code> value; notice the use of the <code>:|</code> <code>NonEmpty</code> data constructor: In all four cases, the tail of the <code>NonEmpty</code> lists only matches the empty list <code>[]</code>. </p> <p> The first rule, for example, has a single variable pattern, where <code>p</code> is the variable name, <em>and</em> a single argument <code>arg</code>, so that pattern matching succeeds and the variable name is bound to the argument. Here's an example: </p> <p> <pre>ghci&gt; tryBind (VarPat "X" :| []) (PairArg TArg FArg :| []) Right [("X",PairArg TArg FArg)]</pre> </p> <p> The result is a variable environment in which the variable name <code>X</code> is bound to the value <code>PairArg TArg FArg</code> (that is, <code>(true, false)</code>). </p> <p> Sometimes, when matching literals, no variables are bound, in which case the environment is empty: </p> <p> <pre>ghci&gt; tryBind (TPat :| []) (TArg :| []) Right []</pre> </p> <p> While the environment itself is empty, the result is still a <code>Right</code> case, indicating that the pattern matched the argument. This, of course, need not be the case: </p> <p> <pre>ghci&gt; tryBind (TPat :| []) (FArg :| []) Left "Could not match FArg :| []."</pre> </p> <p> The rule that attempts to match a pair with a pair argument recursively calls <code>tryBind</code> for the left and the right element, and then uses the <code>Applicative</code> nature of <code>Either</code> to compose those two results. </p> <p> <pre>ghci&gt; tryBind (PairPat TPat (VarPat "Y") :| []) (PairArg TArg FArg :| []) Right [("Y",FArg)]</pre> </p> <p> In this example, you see how a pair pattern composed of <code>(true, Y)</code> matches the argument <code>(true, false)</code>, resulting in the variable environment where <code>Y</code> is bound to <code>false</code>. </p> <p> The final <code>Right</code>-valued match is when there's more than a single pattern, and more than a single argument. In that case, the function recursively calls itself with the heads of each <code>NonEmpty</code> list, as well as the tails of each <code>NonEmpty</code> list. </p> <p> <pre>ghci&gt; tryBind (PairPat TPat (VarPat "Y") :| [VarPat "Z"]) (PairArg TArg FArg :| [PairArg FArg TArg]) Right [("Y",FArg),("Z",PairArg FArg TArg)]</pre> </p> <p> In this example, we try to bind the variables in the patterns <code>(true, Y) Z</code> with the arguments <code>(true, false) (false, true)</code>, producing the variable environment where <code>Y</code> is bound to <code>false</code>, and <code>Z</code> is bound to <code>(false, true)</code>. </p> <p> This exhausts all the legal bindings, so the final, wildcard pattern in <code>tryBind</code> returns a <code>Left</code> value indicating the failure. You've already seen an example of that, above. </p> <p> That function is a bit of a mouthful, but fortunately, we've now covered a major part of the interpreter. </p> <h3 id="e44a976114844acbafec41f24ab0e2bf"> Pattern matching <a href="#e44a976114844acbafec41f24ab0e2bf">#</a> </h3> <p> The <code>tryBind</code> function attempts to bind a single list of patterns to a list of arguments. A function may, however, list several (non-overlapping) rules, so if the first pattern list doesn't match, the interpreter must try the second, the third, and so on, until there are no more patterns to try. While <code>tryBind</code> does the heavy lifting, another function goes through the list of rules. </p> <p> <pre><span style="color:green;">--&nbsp;Goes&nbsp;through&nbsp;one&nbsp;or&nbsp;more&nbsp;function&nbsp;rules,&nbsp;looking&nbsp;for&nbsp;a&nbsp;match. --&nbsp;All&nbsp;the&nbsp;functions&nbsp;in&nbsp;the&nbsp;function&nbsp;list&nbsp;are&nbsp;assumed&nbsp;to&nbsp;have&nbsp;the&nbsp;same&nbsp;name,&nbsp;so --&nbsp;that&nbsp;they&nbsp;are&nbsp;all&nbsp;rules&nbsp;of&nbsp;the&nbsp;same&nbsp;function. --&nbsp;This&nbsp;precondition&nbsp;is&nbsp;not&nbsp;checked&nbsp;here,&nbsp;but&nbsp;handled&nbsp;by&nbsp;the&nbsp;caller.&nbsp;This&nbsp;isn&#39;t --&nbsp;the&nbsp;best&nbsp;implementation&nbsp;decision,&nbsp;but&nbsp;this&nbsp;is,&nbsp;after&nbsp;all,&nbsp;a&nbsp;prototype. </span><span style="color:#2b91af;">tryMatch</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Function</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Arg</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Either</span>&nbsp;[<span style="color:#2b91af;">Char</span>]&nbsp;([(<span style="color:#2b91af;">String</span>,&nbsp;<span style="color:blue;">Arg</span>)],&nbsp;<span style="color:blue;">Exp</span>) tryMatch&nbsp;(Function&nbsp;_&nbsp;pats&nbsp;body&nbsp;:|&nbsp;<span style="color:blue;">[]</span>)&nbsp;args&nbsp;=&nbsp;(,&nbsp;body)&nbsp;&lt;$&gt;&nbsp;tryBind&nbsp;pats&nbsp;args tryMatch&nbsp;(Function&nbsp;_&nbsp;pats&nbsp;body&nbsp;:|&nbsp;(f&nbsp;:&nbsp;fs))&nbsp;args&nbsp;= &nbsp;&nbsp;<span style="color:blue;">case</span>&nbsp;tryBind&nbsp;pats&nbsp;args&nbsp;<span style="color:blue;">of</span> &nbsp;&nbsp;&nbsp;&nbsp;Right&nbsp;b&nbsp;-&gt;&nbsp;Right&nbsp;(b,&nbsp;body) &nbsp;&nbsp;&nbsp;&nbsp;Left&nbsp;_&nbsp;-&gt;&nbsp;tryMatch&nbsp;(f&nbsp;:|&nbsp;fs)&nbsp;args</pre> </p> <p> There are two (Haskell) rules for <code>tryMatch</code>: One where there's only one <code>Function</code> rule, and one where there's more than one. </p> <p> In the first case, <code>tryMatch</code> delegates to <code>tryBind</code>, but if the binding attempt succeeds, also returns the body. </p> <p> <pre>ghci&gt; tryMatch (Function "and" (FPat :| [VarPat "X"]) FExp :| []) (FArg :| [TArg]) Right ([("X",TArg)],FExp)</pre> </p> <p> This example attempts to bind the <em>second</em> rule of the above <code>and</code> function. Compare the input to the AST for <code>and</code> shown above. The result is a tuple where the first, or left, element is the variable environment, and the second, or right, element is the expression that matched. </p> <p> It's important to return the matching expression, since <code>tryMatch</code> doesn't in itself evaluate the <code>body</code>. In case of multiple rules, the interpreter needs to know which body is associated with the matching pattern. </p> <p> <pre>ghci&gt; tryMatch (Function "and" (TPat :| [VarPat "X"]) (VarExp "X") :| [Function "and" (FPat :| [VarPat "X"]) FExp]) (TArg :| [TArg]) Right ([("X",TArg)],VarExp "X") ghci&gt; tryMatch (Function "and" (TPat :| [VarPat "X"]) (VarExp "X") :| [Function "and" (FPat :| [VarPat "X"]) FExp]) (FArg :| [TArg]) Right ([("X",TArg)],FExp)</pre> </p> <p> (Inputs manually formatted for improved readability.) </p> <p> These two examples try to pattern match the above <code>and</code> function. In the first example, the input is <code>true false</code>, which matches the first rule <code>and true X = X</code>. Therefore, the return value is <code>Right ([("X",TArg)],VarExp "X")</code>, indicating a new variable environment in which <code>X</code> is bound to <code>true</code>, and the matching <code>body</code> is <code>VarExp "X"</code>, indicating that the variable <code>X</code> is returned. </p> <p> In the second example, the input is <code>(false, true)</code>, which now matches the second rule <code>and false X = false</code>. The returned tuple now indicates that <code>X</code> is still bound to <code>true</code>, but the returned <code>body</code> is now <code>FExp</code>, indicating the constant return value <code>false</code>. </p> <p> In both cases, <code>tryMatch</code> starts in the second (Haskell) rule, since there are two parameters. In the first example, the first call to <code>tryBind</code> immediately returns a <code>Right</code> result, which is then returned. In the second example, on the other hand, the first call to <code>tryBind</code> returns a <code>Left</code>-value result, which causes <code>tryMatch</code> to recurse back on itself with the remaining (Bopa) rules. </p> <h3 id="8ac0031b02ea46489378c6b25e41ee5e"> Evaluation <a href="#8ac0031b02ea46489378c6b25e41ee5e">#</a> </h3> <p> Given a variable environment and an expression, it's now possible to evaluate the expression to a value. </p> <p> <pre><span style="color:green;">--&nbsp;Evaluate&nbsp;an&nbsp;expression,&nbsp;given&nbsp;a&nbsp;program&nbsp;(AST)&nbsp;and&nbsp;an&nbsp;environment. --&nbsp;Also&nbsp;required&nbsp;as&nbsp;input&nbsp;is&nbsp;a&nbsp;set&nbsp;used&nbsp;for&nbsp;cycle&nbsp;detection.&nbsp;Set&nbsp;elements&nbsp;are --&nbsp;tuples,&nbsp;each&nbsp;consisting&nbsp;of&nbsp;a&nbsp;function&nbsp;identifier&nbsp;(name)&nbsp;and&nbsp;arguments&nbsp;to&nbsp;that --&nbsp;function.&nbsp;If&nbsp;the&nbsp;evaluator&nbsp;recursively&nbsp;sees&nbsp;that&nbsp;tuple&nbsp;again,&nbsp;it&nbsp;has&nbsp;detected --&nbsp;a&nbsp;cycle,&nbsp;and&nbsp;stops&nbsp;further&nbsp;evaluation. </span><span style="color:#2b91af;">eval</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Foldable</span>&nbsp;t &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;<span style="color:blue;">Set</span>&nbsp;(<span style="color:#2b91af;">String</span>,&nbsp;<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Arg</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;t&nbsp;(<span style="color:blue;">NonEmpty</span>&nbsp;<span style="color:blue;">Function</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;[(<span style="color:#2b91af;">String</span>,&nbsp;<span style="color:blue;">Arg</span>)] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">Exp</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Either</span>&nbsp;<span style="color:#2b91af;">String</span>&nbsp;<span style="color:blue;">Arg</span> eval&nbsp;_&nbsp;_&nbsp;env&nbsp;(VarExp&nbsp;name)&nbsp;= &nbsp;&nbsp;<span style="color:blue;">maybe</span>&nbsp;(Left&nbsp;(<span style="color:#a31515;">&quot;Could&nbsp;not&nbsp;find&nbsp;variable&nbsp;&quot;</span>&nbsp;++&nbsp;name&nbsp;++&nbsp;<span style="color:#a31515;">&quot;.&quot;</span>))&nbsp;Right&nbsp;$ &nbsp;&nbsp;<span style="color:blue;">lookup</span>&nbsp;name&nbsp;env eval&nbsp;_&nbsp;_&nbsp;_&nbsp;TExp&nbsp;=&nbsp;Right&nbsp;TArg eval&nbsp;_&nbsp;_&nbsp;_&nbsp;FExp&nbsp;=&nbsp;Right&nbsp;FArg eval&nbsp;observedCalls&nbsp;prog&nbsp;env&nbsp;(CallExp&nbsp;fnid&nbsp;exps)&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;rules&nbsp;&lt;- &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">maybe</span>&nbsp;(Left&nbsp;(<span style="color:#a31515;">&quot;Could&nbsp;not&nbsp;find&nbsp;function&nbsp;&quot;</span>&nbsp;++&nbsp;fnid&nbsp;++&nbsp;<span style="color:#a31515;">&quot;.&quot;</span>))&nbsp;Right&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;find&nbsp;((fnid&nbsp;==)&nbsp;.&nbsp;fid&nbsp;.&nbsp;NE.<span style="color:blue;">head</span>)&nbsp;prog &nbsp;&nbsp;args&nbsp;&lt;-&nbsp;traverse&nbsp;(eval&nbsp;observedCalls&nbsp;prog&nbsp;env)&nbsp;exps &nbsp;&nbsp;(env&#39;,&nbsp;body)&nbsp;&lt;-&nbsp;tryMatch&nbsp;rules&nbsp;args &nbsp;&nbsp;<span style="color:blue;">if</span>&nbsp;Set.member&nbsp;(fnid,&nbsp;args)&nbsp;observedCalls &nbsp;&nbsp;<span style="color:blue;">then</span>&nbsp;Left&nbsp;<span style="color:#a31515;">&quot;Cycle&nbsp;detected.&quot;</span> &nbsp;&nbsp;<span style="color:blue;">else</span>&nbsp;eval&nbsp;(Set.insert&nbsp;(fnid,&nbsp;args)&nbsp;observedCalls)&nbsp;prog&nbsp;env&#39;&nbsp;body</pre> </p> <p> This looks like quite a mouthful, but notice that almost half of this code listing is a comment and a type declaration. </p> <p> As the comment indicates, this function includes cycle detection, which was prompted by the exam questions related to the property of termination. You'll see an example of this later. </p> <p> The <code>eval</code> function pattern matches the four different cases of the <code>Exp</code> sum type. In the first case, if the expression is a variable expression, it tries to <code>lookup</code> the variable in the environment. If found, it's returned; otherwise, an error message is returned. </p> <p> The two next (Haskell) rules simply translate the Boolean representations from patterns to argument values. </p> <p> Finally, if the expression is a function call, more work needs to be done. First, <code>eval</code> tries to <code>find</code> the function in the program. The <code>eval</code> function expects the program <code>prog</code> to be grouped in function rules. For example, it'd expect the above <code>and</code> function to be a <code>NonEmpty</code> list of <code>Function</code> values, and it'd expect, say, <code>alltrue</code> to be another <code>NonEmpty</code> list containing three <code>Function</code> values. </p> <p> If <code>eval</code> finds the named function, it proceeds to evaluate all the expressions (<code>exps</code>) that make up the arguments. It traverses <code>exps</code> and calls itself recursively for each argument. </p> <p> Armed with both <code>rules</code> and <code>args</code> it calls <code>tryMatch</code> to get a new variable environment and the <code>body</code> that matched. If it gets past the cycle detection, it proceeds to call itself recursively with the new environment and the <code>body</code> that matched. </p> <p> Supplying a direct example of calling this function is becoming awkward, as it requires balancing quite a few parentheses, but it <em>can</em> be done. </p> <p> <pre>ghci&gt; eval Set.empty [Function "and" (TPat :| [VarPat "X"]) (VarExp "X") :| [Function "and" (FPat :| [VarPat "X"]) FExp]] [("X",TArg)] TExp Right TArg</pre> </p> <p> (Input manually formatted for improved readability.) </p> <p> This example starts with an empty cycle-detection set, the rules group for <code>and</code>, a variable environment in which <code>X</code> is already bound to <code>true</code>, and evaluates the expression <code>TExp</code> (i.e. <code>true</code>). The result is <code>TArg</code> (i.e. <code>true</code>) wrapped in <code>Right</code>, indicating that evaluation was successful. </p> <h3 id="168b80834d104f60afd002d157c35173"> Interpretation <a href="#168b80834d104f60afd002d157c35173">#</a> </h3> <p> All building blocks for an interpreter are now in place. </p> <p> <pre><span style="color:green;">--&nbsp;Interpret&nbsp;a&nbsp;program&nbsp;(AST),&nbsp;given&nbsp;an&nbsp;entry&nbsp;point&nbsp;and&nbsp;its&nbsp;arguments. </span><span style="color:#2b91af;">interpret</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Foldable</span>&nbsp;f&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;f&nbsp;<span style="color:blue;">Function</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:blue;">Entry</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Either</span>&nbsp;<span style="color:#2b91af;">String</span>&nbsp;<span style="color:blue;">Arg</span> interpret&nbsp;prog&nbsp;(Entry&nbsp;fnid&nbsp;args)&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;functions&nbsp;=&nbsp;&nbsp;NE.groupWith&nbsp;fid&nbsp;prog&nbsp;<span style="color:green;">--&nbsp;Group&nbsp;function&nbsp;rules&nbsp;together </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;The&nbsp;rules&nbsp;that&nbsp;make&nbsp;up&nbsp;`fnid`: </span>&nbsp;&nbsp;rules&nbsp;&lt;- &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">maybe</span>&nbsp;(Left&nbsp;(<span style="color:#a31515;">&quot;Could&nbsp;not&nbsp;find&nbsp;function&nbsp;&quot;</span>&nbsp;++&nbsp;fnid&nbsp;++&nbsp;<span style="color:#a31515;">&quot;.&quot;</span>))&nbsp;Right&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;find&nbsp;((fnid&nbsp;==)&nbsp;.&nbsp;fid&nbsp;.&nbsp;NE.<span style="color:blue;">head</span>)&nbsp;functions &nbsp;&nbsp;(env,&nbsp;body)&nbsp;&lt;-&nbsp;tryMatch&nbsp;rules&nbsp;args &nbsp;&nbsp;eval&nbsp;Set.empty&nbsp;functions&nbsp;env&nbsp;body</pre> </p> <p> This function expects that the program (<code>prog</code>) supplied to it is the raw result of parsing a program. The parser doesn't group identically-named function rules together, so that's the first thing that <code>interpret</code> does. </p> <p> It then proceeds to look through <code>functions</code> to <code>find</code> the function indicated by the entry point. If it succeeds, it calls <code>tryMatch</code> to identify the environment and the body to be evaluated. Finally, it calls <code>eval</code> with these values. </p> <p> <pre>ghci&gt; interpret [Function "and" (TPat :| [VarPat "X"]) (VarExp "X"), Function "and" (FPat :| [VarPat "X"]) FExp] (Entry "and" (TArg :| [TArg])) Right TArg</pre> </p> <p> (Input manually formatted for improved readability.) </p> <p> Like all the above examples, this example processes the <code>and</code> function, calling it with the input values <code>true true</code>, which returns a value representing <code>true</code>, just as we'd expect. </p> <p> The interpreter seems to be working as intended, but it works on the AST. It's time to connect the parsers with the interpreter. </p> <h3 id="5f712e53c8a9463d9629cc5320287217"> Formatting results <a href="#5f712e53c8a9463d9629cc5320287217">#</a> </h3> <p> It'd be more convenient if we feed some source code and a function call into a function and have it spit out the result. In order to make the result prettier, I first added a little formatter for <code>Arg</code>: </p> <p> <pre><span style="color:#2b91af;">formatArg</span>&nbsp;<span style="color:blue;">::</span>&nbsp;<span style="color:blue;">Arg</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">String</span> formatArg&nbsp;TArg&nbsp;=&nbsp;<span style="color:#a31515;">&quot;true&quot;</span> formatArg&nbsp;FArg&nbsp;=&nbsp;<span style="color:#a31515;">&quot;false&quot;</span> formatArg&nbsp;(PairArg&nbsp;a1&nbsp;a2)&nbsp;=&nbsp;<span style="color:#a31515;">&quot;(&quot;</span>&nbsp;++&nbsp;formatArg&nbsp;a1&nbsp;++&nbsp;<span style="color:#a31515;">&quot;,&nbsp;&quot;</span>&nbsp;++&nbsp;formatArg&nbsp;a2&nbsp;++&nbsp;<span style="color:#a31515;">&quot;)&quot;</span></pre> </p> <p> Not surprisingly, <code>formatArg</code> calls itself recursively in order to deal with pairs, and nested pairs. </p> <p> <pre>ghci&gt; formatArg (PairArg TArg (PairArg FArg TArg)) "(true, (false, true))"</pre> </p> <p> It's not really required in order to parse and run a program, but I think that such a function should produce output that looks like the input fed into it. </p> <h3 id="eac1a89bb1994545a48b44a013f4d63c"> Running programs <a href="#eac1a89bb1994545a48b44a013f4d63c">#</a> </h3> <p> All building blocks are now in place to compose a function that parses and runs a program. </p> <p> <pre><span style="color:green;">--&nbsp;Run&nbsp;a&nbsp;given&nbsp;program&nbsp;source&nbsp;and&nbsp;a&nbsp;command&nbsp;that&nbsp;identifies&nbsp;entry&nbsp;point&nbsp;and --&nbsp;arguments. --&nbsp;Despite&nbsp;the&nbsp;generalized&nbsp;type,&nbsp;it&nbsp;can&nbsp;be&nbsp;called&nbsp;as --&nbsp;String&nbsp;-&gt;&nbsp;String&nbsp;-&gt;&nbsp;Either&nbsp;String&nbsp;String </span><span style="color:#2b91af;">run</span>&nbsp;<span style="color:blue;">::</span>&nbsp;(<span style="color:blue;">Stream</span>&nbsp;s1&nbsp;<span style="color:blue;">Identity</span>&nbsp;<span style="color:#2b91af;">Char</span>,&nbsp;<span style="color:blue;">Stream</span>&nbsp;s2&nbsp;<span style="color:blue;">Identity</span>&nbsp;<span style="color:#2b91af;">Char</span>) &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;s1&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;s2&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">Either</span>&nbsp;<span style="color:#2b91af;">String</span>&nbsp;<span style="color:#2b91af;">String</span> run&nbsp;source&nbsp;cmd&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;prog&nbsp;&lt;-&nbsp;first&nbsp;<span style="color:blue;">show</span>&nbsp;$&nbsp;parseProgram&nbsp;source &nbsp;&nbsp;exec&nbsp;&lt;-&nbsp;first&nbsp;<span style="color:blue;">show</span>&nbsp;$&nbsp;parseEntry&nbsp;cmd &nbsp;&nbsp;formatArg&nbsp;&lt;$&gt;&nbsp;interpret&nbsp;prog&nbsp;exec</pre> </p> <p> As the comment suggests, you can call it by feeding it two string literals: </p> <p> <pre>ghci&gt; run "and true X = X\nand false X = false" "and true true" Right "true"</pre> </p> <p> Having to supply entire programs from the REPL gets old fast, however, so instead you can save source code as files. I saved the original examples (containing <code>and</code> and <code>alltrue</code>) in a file named <code>ex.bopa</code>. This enabled me to load the file and call functions in it: </p> <p> <pre>ghci&gt; run &lt;$&gt; readFile "ex.bopa" &lt;*&gt; pure "alltrue (true, (false, true))" Right "false" ghci&gt; run &lt;$&gt; readFile "ex.bopa" &lt;*&gt; pure "alltrue ((true, true), (true, true))" Right "true"</pre> </p> <p> Those are the two examples originally included in the exam set, and fortunately the results are correct. </p> <h3 id="81af068d073e42eebc426b58858d41a7"> A few more examples <a href="#81af068d073e42eebc426b58858d41a7">#</a> </h3> <p> I wanted to subject my code to a bit more testing, so wrote a few more example programs. This one I saved in a file called <code>evenodd.bopa</code>: </p> <p> <pre>and true X = X and false X = false or true X = true or false X = X not true = false not false = true odd true = true odd false = true odd (X, Y) = or (and (odd X) (even Y)) (and (even X) (odd Y)) even X = not (odd X)</pre> </p> <p> The idea with <code>odd</code> is that it indicates whether the input contains an odd number of Boolean values; of course, <code>even</code> is then the negation of <code>odd</code>. </p> <p> <pre>ghci&gt run &lt;$&gt readFile "evenodd.bopa" &lt;*&gt pure "odd true" Right "true" ghci&gt run &lt;$&gt readFile "evenodd.bopa" &lt;*&gt pure "even true" Right "false" ghci&gt run &lt;$&gt readFile "evenodd.bopa" &lt;*&gt pure "odd (true, false)" Right "false" ghci&gt run &lt;$&gt readFile "evenodd.bopa" &lt;*&gt pure "even (true, false)" Right "true" ghci&gt run &lt;$&gt readFile "evenodd.bopa" &lt;*&gt pure "odd (true, (false, true))" Right "true"</pre> </p> <p> Ad hoc tests like these gave me confidence that things aren't completely wrong. </p> <h3 id="fc69d006d0014194a06738695fd79419"> Cycle detection <a href="#fc69d006d0014194a06738695fd79419">#</a> </h3> <p> Finally, you may be curious to see whether the cycle detection works. The simplest example I could come up with was this: </p> <p> <pre>ghci&gt run "forever X = forever X" "forever false" Left "Cycle detected."</pre> </p> <p> Even so, I also wanted to test that it works for a small cycle that involves more than one function, so I saved the following in a file called <code>tictactoe.bopa</code>: </p> <p> <pre>tic X = tac X tac X = toe X toe X = tic X foo (false, Y) = Y foo (true, Y) = tic Y</pre> </p> <p> These functions <em>may</em> cause an infinite cycle, depending on input. </p> <p> <pre>ghci&gt run &lt;$&gt readFile "tictactoe.bopa" &lt;*&gt pure "foo (false, (true, false))" Right "(true, false)" ghci&gt run &lt;$&gt readFile "tictactoe.bopa" &lt;*&gt pure "foo (true, (true, false))" Left "Cycle detected."</pre> </p> <p> The <code>run</code> function implements an algorithm that is <em>always</em> able to determine, in finite time, whether a program terminates or not. Thus, in case you're wondering: The language isn't Turing complete. </p> <h3 id="7de04c9a175a454e8618d2d3ce63f191"> Conclusion <a href="#7de04c9a175a454e8618d2d3ce63f191">#</a> </h3> <p> Implementing a parser and interpreter for the Bopa language wasn't part of the exam question, but I had some time to spare, and also found that I had trouble describing, in unambiguous terms, how to detect termination. I decided to write the interpreter to show a code example, and then took on the parser as <a href="/2020/01/13/on-doing-katas">an extra exercise</a>. </p> <p> It took me a long day of intense coding to produce the prototype shown here, including the various example Bopa programs. No AI was involved. It was fun. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/07/07/a-parser-and-interpreter-for-a-very-small-language Song recommendations from Haskell combinators https://blog.ploeh.dk/2025/06/30/song-recommendations-from-haskell-combinators/ Mon, 30 Jun 2025 05:54:00 UTC <div id="post"> <p> <em>Traversing lists of IO. A refactoring.</em> </p> <p> This article is part of a series named <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">Alternative ways to design with functional programming</a>. In the <a href="/2025/06/23/song-recommendations-from-f-combinators">previous article</a>, you saw how to refactor the example code base to a composition of standard <a href="https://fsharp.org/">F#</a> combinators. It's a pragmatic solution to the problem of dealing with lots of data in a piecemeal fashion, but although it uses concepts and programming constructs from functional programming, I don't consider it a proper <a href="/2018/11/19/functional-architecture-a-definition">functional architecture</a>. </p> <p> You'd expect the <a href="https://www.haskell.org/">Haskell</a> version to be the most idiomatic of the three language variations, but ironically, I had more trouble making the code in this article look nice than I had with the F# variation. You'll see what the problem is later, but it boils down to a combination of Haskell's right-to-left default composition order, and precedence rules of some of the operators. </p> <p> Please consult the previous articles for context about the example code base. The code shown in this article is from the <em>combinators</em> Git branch. It refactors the code shown in the article <a href="/2025/04/21/porting-song-recommendations-to-haskell">Porting song recommendations to Haskell</a>. </p> <p> The goal is to extract <a href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a> from the overall recommendations algorithm and compose them using standard combinators, such as <code>=&lt;&lt;</code>, <code>&lt;$&gt;</code>, and <code>traverse</code>. </p> <h3 id="1a8e6b1d3799463d9c70332949317048"> Getting rid of local mutation <a href="#1a8e6b1d3799463d9c70332949317048">#</a> </h3> <p> My first goal was to get rid of the <code>IORef</code>-based local mutation shown in the <a href="/2025/04/21/porting-song-recommendations-to-haskell">'baseline' code base</a>. That wasn't too difficult. If you're interested in the <a href="https://www.industriallogic.com/blog/whats-this-about-micro-commits/">micro-commits</a> I made to get to that milestone, you can consult the Git repository. The interim result looked like this: </p> <p> <pre>getRecommendations&nbsp;srvc&nbsp;un&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations </span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;Impure </span>&nbsp;&nbsp;scrobbles&nbsp;&lt;-&nbsp;getTopScrobbles&nbsp;srvc&nbsp;un &nbsp;&nbsp;<span style="color:green;">--&nbsp;Pure </span>&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;scrobblesSnapshot&nbsp;=&nbsp;<span style="color:blue;">take</span>&nbsp;100&nbsp;$&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;scrobbleCount)&nbsp;scrobbles &nbsp;&nbsp;<span style="color:green;">--&nbsp;Impure </span>&nbsp;&nbsp;recommendations&nbsp;&lt;- &nbsp;&nbsp;&nbsp;&nbsp;join&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\scrobble&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;join&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\otherListener&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;scrobbledSong&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;10&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating&nbsp;.&nbsp;scrobbledSong)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;(songHasVerifiedArtist&nbsp;.&nbsp;scrobbledSong)&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopScrobbles&nbsp;srvc&nbsp;(userName&nbsp;otherListener))&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;20&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;userScrobbleCount)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;((10_000&nbsp;&lt;=)&nbsp;.&nbsp;userScrobbleCount)&nbsp;=&lt;&lt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopListeners&nbsp;srvc&nbsp;(songId&nbsp;$&nbsp;scrobbledSong&nbsp;scrobble)) &nbsp;&nbsp;&nbsp;&nbsp;scrobblesSnapshot &nbsp;&nbsp;<span style="color:green;">--&nbsp;Pure </span>&nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;<span style="color:blue;">take</span>&nbsp;200&nbsp;$&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating)&nbsp;recommendations</pre> </p> <p> Granted, it's not the most readable way to present the algorithm, but it <em>is</em>, after all, only an intermediate step. As usual, I'll remind the reader that Haskell code should, by default, be read from right to left. When split over multiple lines, this also means that an expression should be read from the bottom to the top. Armed with that knowledge (and general knowledge of Haskell), combined with some helpful indentation, it's not altogether unreadable, but not something I'd like to come back to after half a year. And definitely not something I would foist upon (hypothetical) colleagues. </p> <p> The careful reader may notice that I've decided to use the reverse <em>bind</em> operator <code>=&lt;&lt;</code>, rather than the standard <code>&gt;&gt;=</code> operator. I usually do that with Haskell, because most of Haskell is composed from right to left, and <code>=&lt;&lt;</code> is consistent with that direction. The standard <code>&gt;&gt;=</code> operator, on the other hand, composes monadic actions from left to right. You could argue that that's more natural (to Western audiences), but since everything else stays right-to-left biased, using <code>&gt;&gt;=</code> confuses the reading direction. </p> <p> As a Westerner, I prefer left-to-right reading order, but in general I've found it hard to fight Haskell's bias in the other direction. </p> <p> As the <code>-- Pure</code> and <code>-- Impure</code> comments indicate, interleaving the pure functions with impure actions makes the entire expression impure. The more I do that, the less pure code remains. </p> <h3 id="faf4d26cfb2b42f89449be601e91e582"> Single expression <a href="#faf4d26cfb2b42f89449be601e91e582">#</a> </h3> <p> Going from from the above snapshot to a single impure expression doesn't require many more steps. </p> <p> <pre>getRecommendations&nbsp;srvc&nbsp;un&nbsp;= &nbsp;&nbsp;<span style="color:green;">--&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations </span> &nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;200&nbsp;.&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating)&nbsp;&lt;$&gt; &nbsp;&nbsp;((\scrobbles&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;join&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\scrobble&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;join&nbsp;$ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(\otherListener&nbsp;-&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;scrobbledSong&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;10&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating&nbsp;.&nbsp;scrobbledSong)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;(songHasVerifiedArtist&nbsp;.&nbsp;scrobbledSong)&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopScrobbles&nbsp;srvc&nbsp;(userName&nbsp;otherListener))&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;20&nbsp;&lt;$&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;userScrobbleCount)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;((10_000&nbsp;&lt;=)&nbsp;.&nbsp;userScrobbleCount)&nbsp;=&lt;&lt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getTopListeners&nbsp;srvc&nbsp;(songId&nbsp;$&nbsp;scrobbledSong&nbsp;scrobble)) &nbsp;&nbsp;&nbsp;&nbsp;(<span style="color:blue;">take</span>&nbsp;100&nbsp;$&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;scrobbleCount)&nbsp;scrobbles))&nbsp;=&lt;&lt; &nbsp;&nbsp;getTopScrobbles&nbsp;srvc&nbsp;un)</pre> </p> <p> Neither did it improve readability. </p> <h3 id="91851237845f49a8976aeaaa5a24ebf6"> Helper functions <a href="#91851237845f49a8976aeaaa5a24ebf6">#</a> </h3> <p> As in previous incarnations of this exercise, it helps if you extract some well-named helper functions, like this one: </p> <p> <pre><span style="color:#2b91af;">getUsersOwnTopScrobbles</span>&nbsp;<span style="color:blue;">::</span>&nbsp;[<span style="color:blue;">Scrobble</span>]&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;[<span style="color:blue;">Scrobble</span>] getUsersOwnTopScrobbles&nbsp;=&nbsp;<span style="color:blue;">take</span>&nbsp;100&nbsp;.&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;scrobbleCount)</pre> </p> <p> As a one-liner, that one perhaps isn't that impressive, but none of them are particularly complicated. The biggest function is this: </p> <p> <pre><span style="color:#2b91af;">getTopScrobblesOfOtherUsers</span>&nbsp;<span style="color:blue;">::</span>&nbsp;[<span style="color:blue;">Scrobble</span>]&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;[<span style="color:blue;">Song</span>] getTopScrobblesOfOtherUsers&nbsp;= &nbsp;&nbsp;<span style="color:blue;">fmap</span>&nbsp;scrobbledSong&nbsp;. &nbsp;&nbsp;<span style="color:blue;">take</span>&nbsp;10&nbsp;. &nbsp;&nbsp;sortOn&nbsp;(Down&nbsp;.&nbsp;songRating&nbsp;.&nbsp;scrobbledSong)&nbsp;. &nbsp;&nbsp;<span style="color:blue;">filter</span>&nbsp;(songHasVerifiedArtist&nbsp;.&nbsp;scrobbledSong)</pre> </p> <p> You can see the rest in the Git repository. None of them are exported by the module, which makes them implementation details that you may decide to change or remove at a later date. </p> <p> You can now compose the overall action. </p> <p> <pre>getRecommendations&nbsp;srvc&nbsp;un&nbsp;= &nbsp;&nbsp;<span style="color:green;">--&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations </span> &nbsp;&nbsp;(aggregateTheSongsIntoRecommendations&nbsp;.&nbsp;getTopScrobblesOfOtherUsers)&nbsp;.&nbsp;join&nbsp;&lt;$&gt; &nbsp;&nbsp;((traverse&nbsp;(getTopScrobbles&nbsp;srvc&nbsp;.&nbsp;userName)&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getOtherUsersWhoListenedToTheSameSongs)&nbsp;.&nbsp;join&nbsp;=&lt;&lt; &nbsp;&nbsp;&nbsp;&nbsp;(traverse&nbsp;(getTopListeners&nbsp;srvc&nbsp;.&nbsp;(songId&nbsp;.&nbsp;scrobbledSong))&nbsp;. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getUsersOwnTopScrobbles&nbsp;=&lt;&lt;&nbsp;getTopScrobbles&nbsp;srvc&nbsp;un))</pre> </p> <p> Some of the parentheses break over multiple lines in a non-conventional way. This is my best effort to format the code in a way that emphasises the four steps comprising the algorithm, while still staying within the bounds of the language, and keeping <a href="https://hackage.haskell.org/package/hlint">hlint</a> silent. </p> <p> I could try to argue that if you squint a bit, the operators and other glue like <code>join</code> should <a href="/2018/07/02/terse-operators-make-business-code-more-readable">fade into the background</a>, but in this case, I don't even buy that argument myself. </p> <p> It bothers me that it's so hard to compose the code in a way that approaches being self-documenting. I find that the F# composition in the <a href="/2025/06/23/song-recommendations-from-f-combinators">previous article</a> does a better job of that. </p> <h3 id="f36d4eb691fe4434b89595c5d313f147"> Syntactic sugar <a href="#f36d4eb691fe4434b89595c5d313f147">#</a> </h3> <p> The stated goal in this article is to demonstrate how it's possible to use standard combinators to glue the algorithm together. I've been complaining throughout this article that, while possible, it leaves the code less readable than desired. </p> <p> That one reader who actually knows Haskell is likely frustrated with me. After all, the language does offer a way out. Using the syntactic sugar of <code>do</code> notation, you can instead write the composition like this: </p> <p> <pre>getRecommendations&nbsp;srvc&nbsp;un&nbsp;=&nbsp;<span style="color:blue;">do</span> &nbsp;&nbsp;<span style="color:green;">--&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users </span>&nbsp;&nbsp;<span style="color:green;">--&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations </span> &nbsp;&nbsp;userTops&nbsp;&lt;-&nbsp;getTopScrobbles&nbsp;srvc&nbsp;un&nbsp;&lt;&amp;&gt;&nbsp;getUsersOwnTopScrobbles &nbsp;&nbsp;otherListeners&nbsp;&lt;-&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(getTopListeners&nbsp;srvc&nbsp;.&nbsp;(songId&nbsp;.&nbsp;scrobbledSong))&nbsp;userTops&nbsp;&lt;&amp;&gt; &nbsp;&nbsp;&nbsp;&nbsp;getOtherUsersWhoListenedToTheSameSongs&nbsp;.&nbsp;join &nbsp;&nbsp;songs&nbsp;&lt;- &nbsp;&nbsp;&nbsp;&nbsp;traverse&nbsp;(getTopScrobbles&nbsp;srvc&nbsp;.&nbsp;userName)&nbsp;otherListeners&nbsp;&lt;&amp;&gt; &nbsp;&nbsp;&nbsp;&nbsp;getTopScrobblesOfOtherUsers&nbsp;.&nbsp;join &nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;$&nbsp;aggregateTheSongsIntoRecommendations&nbsp;songs</pre> </p> <p> By splitting the process up into steps with named variables, you can achieve the much-yearned-for top-to-bottom reading order. Taking advantage of the <code>&lt;&amp;&gt;</code> operator from <a href="https://hackage.haskell.org/package/base/docs/Data-Functor.html">Data.Functor</a> we also get left-to-right reading order on each line. </p> <p> That's the best I've been able to achieve under the constraint that the <code>IO</code>-bound operations stay interleaved with pure functions. </p> <h3 id="49db6a22b7924c9cb37813b51f1334e6"> Conclusion <a href="#49db6a22b7924c9cb37813b51f1334e6">#</a> </h3> <p> Mixing pure functions with impure actions like this is necessary when composing whole programs (usually at the entry point; i.e. <code>main</code>), but shouldn't be considered good functional-programming style in general. The entire <code>getRecommendations</code> action is impure, being non-deterministic. </p> <p> Still, even Haskell code eventually needs to compose code in this way. Therefore, it's relevant covering how this may be done. Even so, alternative architectures exist. </p> <p> <strong>Next:</strong> <a href="/2025/07/14/song-recommendations-with-pipes-and-filters">Song recommendations with pipes and filters</a>. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/06/30/song-recommendations-from-haskell-combinators Song recommendations from F# combinators https://blog.ploeh.dk/2025/06/23/song-recommendations-from-f-combinators/ Mon, 23 Jun 2025 05:49:00 UTC <div id="post"> <p> <em>Traversing sequences of tasks. A refactoring.</em> </p> <p> This article is part of a series named <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">Alternative ways to design with functional programming</a>. In the <a href="/2025/06/16/song-recommendations-from-c-combinators">previous article</a>, you saw how to refactor the example code base to a composition of standard combinators. It's a pragmatic solution to the problem of dealing with lots of data in a piecemeal fashion, but although it uses concepts and programming constructs from functional programming, I don't consider it a proper <a href="/2018/11/19/functional-architecture-a-definition">functional architecture</a>. </p> <p> Porting the C# code to <a href="https://fsharp.org/">F#</a> doesn't change that part, but most F# developers will probably agree that this style of programming is more <a href="/2015/08/03/idiomatic-or-idiosyncratic">idiomatic</a> in F# than in C#. </p> <p> Please consult the previous articles for context about the example code base. The code shown in this article is from the <em>fsharp-combinators</em> Git branch. It refactors the code shown in the article <a href="/2025/04/14/porting-song-recommendations-to-f">Porting song recommendations to F#</a>. </p> <p> The goal is to extract <a href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a> from the overall recommendations algorithm and compose them using standard combinators, such as <code>bind</code>, <code>map</code>, and <code>traverse</code>. </p> <h3 id="a83f974b9e474c4a83845e284fb361cc"> Composition from combinators <a href="#a83f974b9e474c4a83845e284fb361cc">#</a> </h3> <p> Let's start with the completed composition, and subsequently look at the most interesting parts. </p> <p> <pre><span style="color:blue;">type</span>&nbsp;<span style="color:#2b91af;">RecommendationsProvider</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">songService</span>&nbsp;:&nbsp;<span style="color:#2b91af;">SongService</span>)&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">member</span>&nbsp;_.GetRecommendationsAsync&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>.<span style="font-weight:bold;color:#74531f;">GetTopScrobblesAsync</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">bind</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">getOwnTopScrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="color:#2b91af;">TaskSeq</span>.<span style="color:#74531f;">traverse</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_.Song.Id &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>.<span style="font-weight:bold;color:#74531f;">GetTopListenersAsync</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">bind</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#74531f;">getTopScrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="color:#2b91af;">TaskSeq</span>.<span style="color:#74531f;">traverse</span>&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_.UserName &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">songService</span>.<span style="font-weight:bold;color:#74531f;">GetTopScrobblesAsync</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">map</span>&nbsp;<span style="color:#74531f;">aggregateRecommendations</span>))) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;&gt;&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">map</span>&nbsp;(<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">flatten</span>&nbsp;&gt;&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">flatten</span>&nbsp;&gt;&gt;&nbsp;<span style="color:#74531f;">takeTopRecommendations</span>))</pre> </p> <p> This is a single expression with nested subexpressions, and you may notice that it's completely <a href="https://en.wikipedia.org/wiki/Tacit_programming">point-free</a>. This may be a little hardcore even for most F# programmers, since F# idiomatically favours explicit lambda expressions and the pipeline operator <code>|&gt;</code>. </p> <p> Although I'm personally fascinated by point-free programming, I might consider a more <code>fun</code> alternative if working in a team. You can see such a variation in the Git repository in an intermediary commit. The reason that I've pulled so heavily in this direction here is that it more clearly demonstrate why we call such functions <em>combinators</em>: They provide the glue that enable us to compose functions together. </p> <p> If you're wondering, <code>&gt;&gt;</code> is also a combinator. In <a href="https://www.haskell.org/">Haskell</a> it's more common with 'unpronounceable' operators such as <code>&gt;&gt;=</code>, <code>.</code>, <code>&amp;</code>, etc., and I'd argue that such <a href="/2018/07/02/terse-operators-make-business-code-more-readable">terse operators can make code more readable</a>. </p> <p> The functions <code>getOwnTopScrobbles</code>, <code>getTopScrobbles</code>, <code>aggregateRecommendations</code>, and <code>takeTopRecommendations</code> are helper functions. Here's one of them: </p> <p> <pre><span style="color:blue;">let</span>&nbsp;<span style="color:blue;">private</span>&nbsp;<span style="color:#74531f;">getOwnTopScrobbles</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span>&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">sortByDescending</span>&nbsp;(<span style="color:blue;">fun</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">s</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">s</span>.ScrobbleCount)&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">truncate</span>&nbsp;100</pre> </p> <p> The other helpers are also simple, single-expression functions like this one. </p> <p> As <a href="https://tyrrrz.me/blog/pure-impure-segregation-principle">Oleksii Holub implies</a>, you could make each of these small functions public if you wished to test them individually. </p> <p> Let's now look at the various building blocks that enable this composition. </p> <h3 id="596f7212bd144ebaa674cbc4e019d892"> Combinators <a href="#596f7212bd144ebaa674cbc4e019d892">#</a> </h3> <p> The F# base library comes with more standard combinators than are generally available for C#, not only for lists, but also <code>Option</code> and <code>Result</code> values. On the other hand, when it comes to <a href="/2022/06/06/asynchronous-monads">asynchronous monads</a>, the F# base library offers <code>task</code> and <code>async</code> <a href="https://learn.microsoft.com/dotnet/fsharp/language-reference/computation-expressions">computation expressions</a>, but no <code>Task</code> module. You'll need to add <code>Task.bind</code> and <code>Task.map</code> yourself, or import a library that exports those combinators. The article <a href="/2022/06/06/asynchronous-monads">Asynchronous monads</a> shows the implementation used here. </p> <p> The <a href="/2024/11/11/traversals">traverse</a> implementation shouldn't be too surprising, either, but here I implemented it directly, instead of via <code>sequence</code>. </p> <p> <pre><span style="color:blue;">let</span>&nbsp;<span style="color:#74531f;">traverse</span>&nbsp;<span style="color:#74531f;">f</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">xs</span>&nbsp;= &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let</span>&nbsp;<span style="color:#74531f;">go</span>&nbsp;<span style="color:#1f377f;">acc</span>&nbsp;<span style="color:#1f377f;">x</span>&nbsp;=&nbsp;<span style="color:blue;">task</span>&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let!</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">x&#39;</span>&nbsp;=&nbsp;<span style="color:#1f377f;">x</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">let!</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">acc&#39;</span>&nbsp;=&nbsp;<span style="color:#1f377f;">acc</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">return</span>&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">append</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">acc&#39;</span>&nbsp;[<span style="font-weight:bold;color:#1f377f;">x&#39;</span>]&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">xs</span>&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">map</span>&nbsp;<span style="color:#74531f;">f</span>&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">fold</span>&nbsp;<span style="color:#74531f;">go</span>&nbsp;(<span style="color:blue;">task</span>&nbsp;{&nbsp;<span style="color:blue;">return</span>&nbsp;[]&nbsp;})</pre> </p> <p> Finally, the <code>flatten</code> function is the <a href="/2022/03/28/monads">standard implementation</a> that goes via monadic <em>bind</em>. In F#'s <code>Seq</code> module, <em>bind</em> is called <code>collect</code>. </p> <p> <pre><span style="color:blue;">let</span>&nbsp;<span style="color:#74531f;">flatten</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">xs</span>&nbsp;=&nbsp;<span style="color:#2b91af;">Seq</span>.<span style="color:#74531f;">collect</span>&nbsp;<span style="color:#74531f;">id</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">xs</span></pre> </p> <p> That all there is to it. </p> <h3 id="2ecbad404afd47a1a4b12e0e68913543"> Conclusion <a href="#2ecbad404afd47a1a4b12e0e68913543">#</a> </h3> <p> In this article, you saw how to port the C# code from the <a href="/2025/06/16/song-recommendations-from-c-combinators">previous article</a> to F#. Since this style of programming is more idiomatic in F#, more building blocks and language features are available, and hence this kind of refactoring is better suited to F#. </p> <p> I still don't consider this proper <a href="/2018/11/19/functional-architecture-a-definition">functional architecture</a>, but it's pragmatic and I could see myself writing code like this in a professional setting. </p> <p> <strong>Next:</strong> <a href="/2025/06/30/song-recommendations-from-haskell-combinators">Song recommendations from Haskell combinators</a>. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/06/23/song-recommendations-from-f-combinators Song recommendations from C# combinators https://blog.ploeh.dk/2025/06/16/song-recommendations-from-c-combinators/ Mon, 16 Jun 2025 07:41:00 UTC <div id="post"> <p> <em>LINQ-style composition, including SelectMany and Traverse.</em> </p> <p> This article is part of a larger series titled <a href="/2025/04/07/alternative-ways-to-design-with-functional-programming">Alternative ways to design with functional programming</a>. In the <a href="/2025/06/09/song-recommendations-from-combinators">previous article</a>, I described, in general terms, a pragmatic small-scale architecture that may <em>look</em> functional, although it really isn't. </p> <p> Please consult the previous articles for context about the example code base. The code shown in this article is from the <em>combinators</em> Git branch. </p> <p> The goal is to extract <a href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a> from the overall recommendations algorithm and compose them using standard combinators, such as <code>SelectMany</code> (<a href="/2022/03/28/monads">monadic <em>bind</em></a>), <code>Select</code>, and <code>Traverse</code>. </p> <h3 id="a667e64d78a346eba94571361ec22fc4"> Composition from combinators <a href="#a667e64d78a346eba94571361ec22fc4">#</a> </h3> <p> Let's start with the completed composition, and subsequently look at the most interesting parts. </p> <p> <pre><span style="color:blue;">public</span>&nbsp;<span style="color:#2b91af;">Task</span>&lt;<span style="color:#2b91af;">IReadOnlyList</span>&lt;<span style="color:#2b91af;">Song</span>&gt;&gt;&nbsp;<span style="font-weight:bold;color:#74531f;">GetRecommendationsAsync</span>(<span style="color:blue;">string</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">userName</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;1.&nbsp;Get&nbsp;user&#39;s&nbsp;own&nbsp;top&nbsp;scrobbles</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;2.&nbsp;Get&nbsp;other&nbsp;users&nbsp;who&nbsp;listened&nbsp;to&nbsp;the&nbsp;same&nbsp;songs</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;3.&nbsp;Get&nbsp;top&nbsp;scrobbles&nbsp;of&nbsp;those&nbsp;users</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;">//&nbsp;4.&nbsp;Aggregate&nbsp;the&nbsp;songs&nbsp;into&nbsp;recommendations</span> &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;_songService.<span style="font-weight:bold;color:#74531f;">GetTopScrobblesAsync</span>(<span style="font-weight:bold;color:#1f377f;">userName</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">SelectMany</span>(<span style="font-weight:bold;color:#1f377f;">scrobbles</span>&nbsp;=&gt;&nbsp;<span style="color:#74531f;">UserTopScrobbles</span>(<span style="font-weight:bold;color:#1f377f;">scrobbles</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Traverse</span>(<span style="font-weight:bold;color:#1f377f;">scrobble</span>&nbsp;=&gt;&nbsp;_songService &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">GetTopListenersAsync</span>(<span style="font-weight:bold;color:#1f377f;">scrobble</span>.Song.Id) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Select</span>(<span style="color:#74531f;">TopListeners</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">SelectMany</span>(<span style="font-weight:bold;color:#1f377f;">users</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">users</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Traverse</span>(<span style="font-weight:bold;color:#1f377f;">user</span>&nbsp;=&gt;&nbsp;_songService &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">GetTopScrobblesAsync</span>(<span style="font-weight:bold;color:#1f377f;">user</span>.UserName) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Select</span>(<span style="color:#74531f;">TopScrobbles</span>)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Select</span>(<span style="color:#74531f;">Songs</span>))) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Select</span>(<span style="color:#74531f;">TakeTopRecommendations</span>)); }</pre> </p> <p> This is a single expression with nested subexpressions. </p> <p> The functions <code>UserTopScrobbles</code>, <code>TopListeners</code>, <code>TopScrobbles</code>, <code>Songs</code>, and <code>TakeTopRecommendations</code> are <code>private</code> helper functions. Here's one of them: </p> <p> <pre><span style="color:blue;">private</span>&nbsp;<span style="color:blue;">static</span>&nbsp;<span style="color:#2b91af;">IEnumerable</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;&nbsp;<span style="color:#74531f;">UserTopScrobbles</span>(<span style="color:#2b91af;">IEnumerable</span>&lt;<span style="color:#2b91af;">Scrobble</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobbles</span>.<span style="font-weight:bold;color:#74531f;">OrderByDescending</span>(<span style="font-weight:bold;color:#1f377f;">scrobble</span>&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">scrobble</span>.ScrobbleCount).<span style="font-weight:bold;color:#74531f;">Take</span>(100); }</pre> </p> <p> The other helpers are also simple, single-expression functions like this one. </p> <p> As <a href="https://tyrrrz.me/blog/pure-impure-segregation-principle">Oleksii Holub implies</a>, you could make each of these small functions <code>public</code> if you wished to test them individually. </p> <p> Let's now look at the various building blocks that enable this composition. </p> <h3 id="2d6d18db84d84ada8084fd9a035918d6"> Asynchronous monad <a href="#2d6d18db84d84ada8084fd9a035918d6">#</a> </h3> <p> C# (or .NET) in general only comes with standard combinators for <a href="https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1">IEnumerable&lt;T&gt;</a>, so whenever you need them for other <a href="/2022/03/28/monads">monads</a>, you have to define them yourself (or pull in a reusable library that defines them). For the above composition, you'll need <code>SelectMany</code> and <code>Select</code> for <code>Task</code> computations. You can see implementations in the article <a href="/2022/06/06/asynchronous-monads">Asynchronous monads</a>, so I'll not repeat the code here. </p> <p> One exception is this extension method, which is a variant monadic <em>return</em>, which I'm not sure if I've published before: </p> <p> <pre><span style="color:blue;">internal</span>&nbsp;<span style="color:blue;">static</span>&nbsp;<span style="color:#2b91af;">Task</span>&lt;<span style="color:#2b91af;">T</span>&gt;&nbsp;<span style="color:#74531f;">AsTask</span>&lt;<span style="color:#2b91af;">T</span>&gt;(<span style="color:blue;">this</span>&nbsp;<span style="color:#2b91af;">T</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">source</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;<span style="color:#2b91af;">Task</span>.<span style="color:#74531f;">FromResult</span>(<span style="font-weight:bold;color:#1f377f;">source</span>); }</pre> </p> <p> Nothing much is going on here, since it's just a wrapper of <a href="https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.fromresult">Task.FromResult</a>. The <code>this</code> keyword, however, makes <code>AsTask</code> an extension method, which makes usage marginally prettier. It's not used in the above composition, but, as you'll see below, in the implementation of <code>Traverse</code>. </p> <h3 id="f71260c45e2b45df8a778db7182f5519"> Traversal <a href="#f71260c45e2b45df8a778db7182f5519">#</a> </h3> <p> The <a href="/2024/11/11/traversals">traversal</a> could be implemented from a hypothetical <code>Sequence</code> action, but you can also implement it directly, which is what I chose to do here. </p> <p> <pre><span style="color:blue;">internal</span>&nbsp;<span style="color:blue;">static</span>&nbsp;<span style="color:#2b91af;">Task</span>&lt;<span style="color:#2b91af;">IEnumerable</span>&lt;<span style="color:#2b91af;">TResult</span>&gt;&gt;&nbsp;<span style="color:#74531f;">Traverse</span>&lt;<span style="color:#2b91af;">T</span>,&nbsp;<span style="color:#2b91af;">TResult</span>&gt;( &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">this</span>&nbsp;<span style="color:#2b91af;">IEnumerable</span>&lt;<span style="color:#2b91af;">T</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">source</span>, &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Func</span>&lt;<span style="color:#2b91af;">T</span>,&nbsp;<span style="color:#2b91af;">Task</span>&lt;<span style="color:#2b91af;">TResult</span>&gt;&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">selector</span>) { &nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#8f08c4;">return</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">source</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Select</span>(<span style="font-weight:bold;color:#1f377f;">selector</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Aggregate</span>( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Enumerable</span>.<span style="color:#74531f;">Empty</span>&lt;<span style="color:#2b91af;">TResult</span>&gt;().<span style="font-weight:bold;color:#74531f;">AsTask</span>(), &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">async</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">acc</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">x</span>)&nbsp;=&gt;&nbsp;(<span style="color:blue;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">acc</span>).<span style="font-weight:bold;color:#74531f;">Append</span>(<span style="color:blue;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">x</span>)); }</pre> </p> <p> Mapping <code>selector</code> over <code>source</code> produces a sequence of tasks. The <code>Aggregate</code> expression subsequently inverts the <a href="https://bartoszmilewski.com/2014/01/14/functors-are-containers/">containers</a> to a single task that contains a sequence of result values. </p> <p> That's really all there is to it. </p> <h3 id="b717fff9dd2b4bdb826dc62a3d3a9050"> Conclusion <a href="#b717fff9dd2b4bdb826dc62a3d3a9050">#</a> </h3> <p> In the <a href="/2025/06/09/song-recommendations-from-combinators">previous article</a>, I made no secret of my position on this refactoring. For the example at hand, the benefit is at best marginal. The purpose of this article isn't to insist that you must write code like this. Rather, it's a demonstration of what's possible. </p> <p> If you have a problem which is similar, but more complicated, refactoring to standard combinators <em>may</em> be a good idea. After all, a standard combinator like <code>SelectMany</code>, <code>Traverse</code>, etc. is well-understood and lawful. You should expect combinators to be defect-free, so using them instead of ad-hoc code constructs like nested loops with conditionals could help eliminate some trivial bugs. </p> <p> Additionally, if you're working with a team comfortable with these few abstractions, code assembled from standard combinators may actually turn out to be <em>more</em> readable that code buried in ad-hoc imperative <a href="https://en.wikipedia.org/wiki/Control_flow">control flow</a>. And if not everyone on the team is on board with this style, perhaps it's <a href="/2015/08/03/idiomatic-or-idiosyncratic">an opportunity to push the envelope a bit</a>. </p> <p> Of course, if you use a language where such constructs are already idiomatic, colleagues should already be used to this style of programming. </p> <p> <strong>Next:</strong> <a href="/2025/06/23/song-recommendations-from-f-combinators">Song recommendations from F# combinators</a>. </p> </div><hr> This blog is totally free, but if you like it, please consider <a href="https://blog.ploeh.dk/support">supporting it</a>. Mark Seemann https://blog.ploeh.dk/2025/06/16/song-recommendations-from-c-combinators