<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://joka.codeberg.page/magpie-madhouse/feed.xml" rel="self" type="application/atom+xml" /><link href="https://joka.codeberg.page/magpie-madhouse/" rel="alternate" type="text/html" /><updated>2026-04-15T18:53:28+00:00</updated><id>https://joka.codeberg.page/magpie-madhouse/feed.xml</id><title type="html">Magpie Madhouse</title><subtitle>(Functional) Programming, Media Analysis, and More
</subtitle><author><name>Josefine Katzke</name><email>jokatzke@fastmail.com</email></author><entry><title type="html">The Struggle of Finding a Home for My Book Ratings</title><link href="https://joka.codeberg.page/magpie-madhouse/personal/media/books/2026/04/15/finding-a-place-to-rate-books.html" rel="alternate" type="text/html" title="The Struggle of Finding a Home for My Book Ratings" /><published>2026-04-15T00:00:00+00:00</published><updated>2026-04-15T00:00:00+00:00</updated><id>https://joka.codeberg.page/magpie-madhouse/personal/media/books/2026/04/15/finding-a-place-to-rate-books</id><content type="html" xml:base="https://joka.codeberg.page/magpie-madhouse/personal/media/books/2026/04/15/finding-a-place-to-rate-books.html"><![CDATA[<p>
In the last year or so, I've started engaging with media very differently than how I used to; rather than being a passive consumer, I want to be an active participant in the art. A pretty important part of that, to me, is a good place to track, rate, and review said media.
</p>

<p>
I already found some pretty solid options for movies (<a href="https://letterboxd.com/joka_/">Letterboxd</a>) and video games (<a href="https://backloggd.com/u/joka__/">Backloggd</a>), but books have been a bit of a struggle. In this post, I want to document this struggle a bit. Hopefully, this can help anyone in a similar situation.
</p>
<div id="outline-container-org29a8220" class="outline-2">
<h2 id="org29a8220">My Needs</h2>
<div class="outline-text-2" id="text-org29a8220">
<p>
Comparing different solution is always kind of pointless when you don't know how you are comparing them or what exactly it is you want. Luckily, given my experience with other sites, I've got a pretty good idea as to what I want, namely:
</p>

<dl class="org-dl">
<dt><b>No Big Tech</b></dt><dd>This immediately disqualifies the biggest option, Goodreads.</dd>
</dl>


<dl class="org-dl">
<dt><b>No Lock-In</b></dt><dd>I refuse to be a victim of <a href="https://craphound.com/category/enshittification/">enshittification</a> (hence me also wanting to avoid big tech). Thus, I need do be able to relatively easily export my data so I can switch to an alternative.</dd>
</dl>


<dl class="org-dl">
<dt><b>Organize Ratings</b></dt><dd>I frequently go over my rating and reevaluate them. Not being able to sort them, especially by rating, makes this quite painful.</dd>
</dl>


<dl class="org-dl">
<dt><b>Social Features</b></dt><dd>I strongly dislike algorithmic recommendations; instead, I want to follow other people with similar tastes and get recommendations organically.</dd>
</dl>


<dl class="org-dl">
<dt><b>High-Quality Review Culture</b></dt><dd>This goes hand-in-hand with the previous point. I want to read actually interesting, relatively in-depth reviews. Some quips / micro-reviews are fine, but that shouldn't be all you find. A good way of achieving this is by letting users rate reviews and being able to sort them accordingly.</dd>
</dl>


<dl class="org-dl">
<dt><b>No Account Required</b></dt><dd>I want to be able to easily share my profile with other people, so they can see what my tastes are like / what I've already read.</dd>
</dl>
</div>
</div>
<div id="outline-container-org8deb316" class="outline-2">
<h2 id="org8deb316">The StoryGraph</h2>
<div class="outline-text-2" id="text-org8deb316">
<p>
The StoryGraph was, I think, the first site I went to after leaving Goodreads. It's also, from what I can tell, the most popular alternative, which is a plus. So let's go over each of my requirements and evaluate how fully they are realized.
</p>

<dl class="org-dl">
<dt><b>No Big Tech</b></dt><dd>The site is run by a small group of independent creators. Definitely a plus.</dd>
</dl>


<dl class="org-dl">
<dt><b>No Lock-In</b></dt><dd>You can easily export your data. I actually made use of this feature before, by migrating to BookWyrm (more on that later).</dd>
</dl>


<dl class="org-dl">
<dt><b>Organize Ratings</b></dt><dd>This is where The StoryGraph truly shines; it lets you sort all of your lists in various ways and even allows you to filter them, e.g. by year read or genre.</dd>
</dl>


<dl class="org-dl">
<dt><b>Social Features</b></dt><dd>You can tell that this site wasn't really designed around a social experience. While it's get the basics like being able to follow other users and seeing their activities in a separate tab, this isn't the default experience. You have to go to a separate tab to see any of your friends' activities. Social interactions also appear to be limited to your interactions with books, with no ability to post thoughts or updates separately.</dd>
</dl>


<dl class="org-dl">
<dt><b>High-Quality Review Culture</b></dt><dd><p>
Interestingly enough, this, in my opinion, where The StoryGraph is weakest. While, in theory, the building blocks are there (you can write reviews, they are shown to people, they can like them, and you can sort by most liked), this feature feels basically hidden.
</p>

<p>
On the front-page of a book, you can only see the average rating of the book; again, you have to go out of your way to see any more details, like the rating distribution or any written reviews. I have to assume that this is also why I haven't seen many reviews with more than five likes and none with more than ten &ndash; nobody reads them! Anecdotally, I can also confirm this because on both The StoryGraph and Fable, I posted the exact same reviews for two books. On the former, I have gotten absolutely no interactions from those, whereas on the second, I received three likes on one and a few follows within just a few days.
</p>

<p>
To make matters worse, The StoryGraph appears to have, on average, the least interesting, shallowest reviews of any of the sites I've tried. Again, I can only speculate as to why this is. Some of it must be my previous observation of reviews kind of being hidden. Maybe it's also because, philosophically, the site is very much designed to be "data-driven" (more on that later), so people that are more interested in organic interactions don't stick.
</p></dd>
</dl>


<dl class="org-dl">
<dt><b>No Account Required</b></dt><dd>Weirdly enough, you can browse basically everything on the site except for user profiles without being logged in. Makes absolutely no sense to me.</dd>
</dl>


<dl class="org-dl">
<dt><b>Additional Observations</b></dt><dd><p>
I already mentioned how The StoryGraph feels very "data-driven". By that I mean that it appears to try to make book ratings "more objective" by focusing on some select statistics, rather than individual reviews.
</p>

<p>
And I kind of hate it. The spectrum of what books can offer is WAY too vast as to be captured by a few tags, moods, genres, and six weird metrics. I especially dislike the metrics &ndash; "Pace", "Plot or character driven?", "Strong character development?", "Loveable characters?", "Diverse cast of characters?", and "Flaws of characters a main focus?". They feel like they were specifically designed for one or two genres (namely genre fiction and romance) that focus way too much on the "plot". Like, why is there nothing about the prose? Or whether the book is trying to say something or just wants to be entertaining? And they are really vague! For example, a "diverse cast of characters" can mean a lot of different things to a lot of different people.
</p>

<p>
But if you think about it, this plot / story focused approach makes a lot of sense for a site with "story" in its title. Guess it's just not for me.
</p></dd>

<dt><b>Final Thoughts</b></dt><dd>Even though the library management features of The StoryGraph are sufficient for my needs, I don't think I will be sticking around on this platform, simply because it kind of gives me the ick.</dd>
</dl>
</div>
</div>
<div id="outline-container-org4670e6b" class="outline-2">
<h2 id="org4670e6b">BookWyrm</h2>
<div class="outline-text-2" id="text-org4670e6b">
<p>
During my exploratory phase with Mastodon around two and a half years ago, I also moved from The StoryGraph to <a href="https://bookwyrm.social/">bookwyrm.social</a>, the flagship, and largest, instance of BookWyrm. Just like Mastodon (or E-Mail), it is federated, meaning anyone can set up an instance for people to join whilst being able to interact with people from other instance.
</p>

<p>
Unfortunately, the fact that BookWyrm is a volunteer-run open-source project means that it doesn't have nearly as much funding as many other options, which in turn means fewer features.
</p>

<dl class="org-dl">
<dt><b>No Big Tech or Lock-In</b></dt><dd>Decentralized services like this are essentially designed to be proofed against lock-in and big tech. You can easily export all of your data and even <a href="https://docs.joinbookwyrm.com/user-migration.html">migrate between instances</a>. This is by far the best option if you are serious about owning your data.</dd>
</dl>


<dl class="org-dl">
<dt><b>Organize Ratings</b></dt><dd>Probably one of the weakest points of BookWyrm; its organizational features are extremely rudimentary. You can create custom lists, but you cannot sort or meaningfully group their contents. They are always sorted anti-chronologically.</dd>
</dl>


<dl class="org-dl">
<dt><b>Social Features</b></dt><dd>BookWyrm very much feels like "Mastodon, but for books". The homepage consists of an anti-chronological feed of all of your friends' activities, which you can interact with by commenting, sharing, or liking. However, just like with The StoryGraph, the feed is limited to user's interactions with books; you can't freely post about something.</dd>
</dl>


<dl class="org-dl">
<dt><b>High-Quality Review Culture</b></dt><dd><p>
Unsurprisingly, given that this is one of the most niche ways to track your books, you'll find few to no reviews on any given book, especially if they are a bit older and/or less popular. However, I did find that, on average, the reviews that do exist are very high quality. They tend to be quite long and detailed, and you don't have to sift through "reviews" with no text to get to those that actually contain a human's thoughts.
</p>

<p>
Unfortunately, liking reviews doesn't appear to really do anything(?) &ndash; you cannot, for example, sort a book's reviews by rating. You can't even see how many people liked one.
</p>

<p>
Also, you cannot see the distribution of rating; you are only shown the average. This makes that number pretty much useless &ndash; a 3.5 / 5, for example, could indicate a mediocre book or a divisive one (with both very positive and very negative reception). Without a distribution, you're basically back to guessing.
</p></dd>
</dl>


<dl class="org-dl">
<dt><b>No Account Required</b></dt><dd>From what I can tell, you can browse an instance's entire contents, including user profiles, without needing to create an account. So that's another plus.</dd>
</dl>


<dl class="org-dl">
<dt><b>Additional Observations</b></dt><dd><p>
<code>bookwyrm.social</code> can, at times, be pretty slow to load and react. I assume that is due to running on volunteer-run hardware with minimal budget. Maybe other instances are better, though.
</p>

<p>
Also, there are lots of paper-cuts to be found; this is definitely not a very polished product. For example, the search is really finicky with basically no optimization (searching for <code>the way of igs</code> will get you no results). Also, there are lots and lots of duplicates, both for books and authors. Annoyingly, different copies of the same book will usually link to different copies of the same author and some of the latter may only have a small subset of their works linked.
</p></dd>
</dl>


<dl class="org-dl">
<dt><b>Final Thoughts</b></dt><dd>BookWyrm is honestly pretty close for me. I especially like the fact that it is open-source and federated. However, there are just so. many. paper-cuts. Maybe I'll have to look into contributing to the project myself.</dd>
</dl>
</div>
</div>
<div id="outline-container-org1623a1a" class="outline-2">
<h2 id="org1623a1a">Fable</h2>
<div class="outline-text-2" id="text-org1623a1a">
<p>
I'll break with the formula here and cut this short: <a href="https://fable.co/">Fable</a> seems like a really promising option with a heavy focus on social reading. However, there are some pretty glaring issues that make me not want to use it. Namely, it feels like a walled garden (with impressively high walls at that). There is no option to export your collection, <a href="https://www.reddit.com/r/Booktokreddit/comments/1mnrr1m/fable_giving_no_way_to_export_data/">and apparently, there are no plans of adding one</a>. There is absolutely no way to interact with anything on the site without an account; can't even browse books. And, to add insult to injury, essentially the only way to interact with the service is through their app &ndash; the website version is extremely bare-bones and allows you to do little more than view your own profile.
</p>

<p>
So yea, no thanks. The entire point of this exercise was to avoid closed ecosystems like this.
</p>
</div>
</div>
<div id="outline-container-orgfc6b389" class="outline-2">
<h2 id="orgfc6b389">Hardcover</h2>
<div class="outline-text-2" id="text-orgfc6b389">
<p>
I should preface this with noting that I have used <a href="https://hardcover.app">Hardcover</a> only for a little bit in order to get a feel for them. However, I tried simulating relatively realistic usage by intentionally not import my data from anywhere and instead adding a subset of my read books and reviews manually.
</p>

<p>
Apart from BookWyrm, <a href="https://hardcover.app">Hardcover</a> is definitely has the smallest community of the options I have explored so far. Going by their 2025 recap, they grew from 25 thousand users to 60 thousand in that year, which is definitely an impressive growth rate. But in comparison to The StoryGraph, which just recently surpassed 5 million users, that is still tiny. But 60 thousand is still plenty, I think.
</p>

<dl class="org-dl">
<dt><b>No Big Tech</b></dt><dd>Another website run by independent creators. Yay.</dd>
</dl>


<dl class="org-dl">
<dt><b>No Lock-In</b></dt><dd>You can easily export your data to <code>csv</code>.</dd>
</dl>


<dl class="org-dl">
<dt><b>Organize Ratings</b></dt><dd>While not as extensive as The StoryGraph, Hardcover's organizational features seem pretty full-fledged, at least for my needs. Apart from the default lists, you can create custom ones and you can sort entries by various metrics, including your rating.</dd>
</dl>


<dl class="org-dl">
<dt><b>Social Features</b></dt><dd><p>
All of the essentials appear to be present. You can follow people, which puts them in a feed that shows you their recent activities. This feed is also pretty central to your home-page, giving you an overview of the most recent activity at a glance.
</p>

<p>
I quite like the prompt feature they've got, which lets you answer a shared prompt with a collection of books and an explanation as to why you put them there. As a software engineer, I love a good abstraction and this is an awesome way of taking the "what are your favorite books" section of the profile and extend it to other applications.
</p>

<p>
A particularly fun feature of user profiles is that you can sort other people's lists by <i>your ratings</i>. I've never seen that feature before but I will definitely be making use of it here.
</p></dd>
</dl>


<dl class="org-dl">
<dt><b>High-Quality Review Culture</b></dt><dd>At first glance, the review culture on Hardcover seems to be pretty solid. One of my go-to books for evaluating reviews is Authority, a) because I love it, b) because it's relatively old and niche, and c) because it's pretty divisive. While there seems to be basically no interactions with the reviews for it, I found a few pretty solid once quickly, which is encouraging. I also looked at the reviews for Project Hail Mary and there, there was a lot more traction on popular reviews.</dd>
</dl>

<p>
On that note, reviews are sorted by the number of their likes by default, which I appreciate, but there doesn't appear to be any other way to sort them; you can just filter by a specific rating range. Still, this is a sensible default and probably good enough for me.
</p>


<dl class="org-dl">
<dt><b>No Account Required</b></dt><dd>From what I can tell, everything, including profiles, is publicly available (assuming the profile is set to public, of course).</dd>
</dl>


<dl class="org-dl">
<dt><b>Additional Observations</b></dt><dd><p>
As an aspiring blogger, I love the fact that you can add a canonical URL to your review. I haven't seen any reviews using that feature though, so it's either not used much or really not all that visible.
</p>

<p>
Also, as probably became obvious from the section on The StoryGraph, I despise recommendation algorithms. Thus, I was pleasantly surprised when I found out that the Explore section consists of a bunch of different ways of browsing books, lists, users, etc.
</p>

<p>
Another pleasant surprise was the fact that Hardcover appears to be low on trackers and web nonsense. I've got set up my uBlock to block all third-party JavaScript scripts and the site works just fine without me needing to allowlist anything. Kudos!
</p></dd>
</dl>


<dl class="org-dl">
<dt><b>Final Thoughts</b></dt><dd>I am a bit shocked to find that I have absolutely no issues with Hardcover. In fact, I keep on being pleasantly surprised whilst exploring the site. So, I think I found a new home for my book ratings! You can find as <a href="https://hardcover.app/@josi_?referrer_id=91327">@josi_</a></dd>
</dl>
</div>
</div>
<div id="outline-container-orge92d4b3" class="outline-2">
<h2 id="orge92d4b3">Final Final Thoughts</h2>
<div class="outline-text-2" id="text-orge92d4b3">
<p>
I am honestly a bit shocked that this journey / rabbit hole appears to have come to an end, especially so soon. I still had quite a few other options on my list to check out, like LibraryThing, Tome, Oku, Literal.club, Papertrail, or BookSloth. But after checking out what all these sites have to offer, and how they don't fit my needs (apart from Hardcover), I am honestly getting a bit tired of this topic and am definitely quite satisfied with Hardcover.
</p>

<p>
This may not be the "optimal" choice; there may be another service out there that does everything that Hardcover does and more and / or better. But there is this little saying you may have heard: Perfect is the enemy of good. <i>Admittedly, looking at my past actions and decisions, I certainly don't appear to have heard of it, or, if I did, I must be choosing to ignore it.</i>
</p>

<p>
I hope this silly overview of book rating websites that was somehow both way too detailed and also way too narrow may be of help to someone else. It was definitely helpful for me. See you on Hardcover! :)
</p>
</div>
</div>]]></content><author><name>Josefine Katzke</name><email>jokatzke@fastmail.com</email></author><category term="Personal" /><category term="Media" /><category term="Books" /><summary type="html"><![CDATA[In the last year or so, I've started engaging with media very differently than how I used to; rather than being a passive consumer, I want to be an active participant in the art. A pretty important part of that, to me, is a good place to track, rate, and review said media.]]></summary></entry><entry><title type="html">Map Filter Reduce is not the Point – Part 0, Introduction</title><link href="https://joka.codeberg.page/magpie-madhouse/programming/2026/04/02/map-filter-reduce-is-just-the-start_p0.html" rel="alternate" type="text/html" title="Map Filter Reduce is not the Point – Part 0, Introduction" /><published>2026-04-02T00:00:00+00:00</published><updated>2026-04-02T00:00:00+00:00</updated><id>https://joka.codeberg.page/magpie-madhouse/programming/2026/04/02/map-filter-reduce-is-just-the-start_p0</id><content type="html" xml:base="https://joka.codeberg.page/magpie-madhouse/programming/2026/04/02/map-filter-reduce-is-just-the-start_p0.html"><![CDATA[<p>
Hi! Quick disclaimer: this is my first ever blog post, so my writing may still be a bit rough. I hope you might still get something out of it though :)
</p>

<p>
<b>Target audience</b>: Anyone interested in functional programming and relatively new to it; I won't assume any knowledge about it.
</p>
<div id="outline-container-org11f2079" class="outline-2">
<h2 id="org11f2079">The Idea of this Series</h2>
<div class="outline-text-2" id="text-org11f2079">
<p>
For this series of blog posts, I want to clear up some common misconceptions I've seen people make whilst they are first learning about functional programming, and the "<code>map</code>, <code>filter</code>, <code>reduce</code> pattern" in particular. These basically boil down to "I can achieve the same thing with a <code>for</code>-loop / list-comprehension".
</p>

<p>
A good example of this is <a href="https://www.youtube.com/watch?v=ylzo04lU9Xs">ArjanCodes' video on map and filter</a>, which primarily compares directly replacing loops and <code>if</code>-statements with <code>map</code> and <code>filter</code>.
</p>

<p>
I see two perspectives that are missing from these analyses, both of which I will be going into more depth in my follow-up blog posts:
</p>
<ol class="org-ol">
<li><code>map</code>, <code>filter</code>, <code>reduce</code> is a way of thinking that is easily extended onto more complex problems.
There is a whole family of functions that are just about acting on data in a structured way.
Alternative approaches like <code>for</code>-loops and list-comprehensions don't really work like this; they are a one-size-fits-all solution.</li>
<li><code>map</code>, <code>filter</code>, and <code>reduce</code> are functions, so they can be manipulated using functional techniques like partial function application and data processing pipelines; they are <i>first-class citizens</i> in functional languages.
<code>for</code>-loops and list-comprehensions are syntax and <i>second-class citizens</i> in most languages.</li>
</ol>

<p>
For code examples, I'll be using Python. The reason for this is that, even though I personally don't really like Python, I'm pretty comfortable with it, it's got decent support for functional programming, and most people should be able to read it relatively well. I'll also add some basic type-hinting to all of my examples. It should be pretty safe to ignore these if you want, especially since later on they will get a bit more involved.
</p>

<dl class="org-dl">
<dt>Side Note</dt><dd>Readability™ also, gets thrown around a lot, but I won't address that. I'll just say that I consider the kind of readability issues most people seem to have to really be about not being used to these tools and refer you to <a href="https://youtu.be/SxdOUGdseq4?si=Rs39a73pM0I1QVQX">Rich Hickey's excellent talk "Simple Made Easy"</a>.</dd>
</dl>
</div>
</div>
<div id="outline-container-orged3bbe1" class="outline-2">
<h2 id="orged3bbe1">What is <code>map</code>, <code>filter</code>, <code>reduce</code>?</h2>
<div class="outline-text-2" id="text-orged3bbe1">
<p>
Just so we are all on the same page, I want to dedicate the rest of this introductory post to defining these operations I've been talking about. In short, <code>map</code>, <code>filter</code>, and <code>reduce</code> are functions that allow us to act on collections of values "all at once". Each is an example of a higher-order function, meaning that they take another function as an argument. This may seem a bit weird at first but is actually an extremely powerful idea. In object-oriented programming, you may call this a strategy pattern.
</p>

<p>
<i>Feel free to skip this if you are already familiar with these operations.</i>
</p>
</div>
<div id="outline-container-orgfb9294d" class="outline-3">
<h3 id="orgfb9294d"><code>map</code></h3>
<div class="outline-text-3" id="text-orgfb9294d">
<p>
<code>map</code> is probably the easiest to understand of the bunch. In the simplest case, it takes
</p>
<ul class="org-ul">
<li>a function <code>f</code> with one argument and some kind of return value and</li>
<li>a collection <code>coll</code> of values</li>
</ul>
<p>
and returns a new collection where <code>f</code> was applied on each value in <code>coll</code>.
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">the function we want to apply
</span>def <span style="color: #721045;">f</span>(x: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">int</span>:
    return x * 2

<span style="color: #005e8b;">coll</span> = <span style="color: #8f0075; font-weight: bold;">list</span>(<span style="color: #8f0075; font-weight: bold;">range</span>(10))

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"original:      "</span>, coll)

<span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">in python, `map` is lazy; we have to convert its result to a list to actually see it
</span><span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"no list conv:  "</span>, <span style="color: #8f0075; font-weight: bold;">map</span>(f, coll))
<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"mapped:        "</span>, <span style="color: #8f0075; font-weight: bold;">list</span>(<span style="color: #8f0075; font-weight: bold;">map</span>(f, coll)))
</code></pre>
</div>

<pre class="example">
original:       [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
no list conv:   &lt;map object at 0x1001f41f0&gt;
mapped:         [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
</pre>

<p>
Notice how, after applying <code>map</code>, the shape of the given collection does not change, only its contents do.
</p>

<p>
We can achieve the same effect using list-comprehensions:
</p>
<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"list-comprehension:"</span>, [f(x) for x in coll])
</code></pre>
</div>

<pre class="example">
list-comprehension: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
</pre>

<p>
At the beginning, I said "in the simplest case" because <code>map</code> actually works with functions of any arity. For example, we could add two sequences of numbers pair-wise using the following code:
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">in python, there is no function for adding two numbers,
</span><span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">so we have to define our own.
</span><span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">(well technically there is, in the `operator` module, but I'll still define it here)
</span>def <span style="color: #721045;">add</span>(x: <span style="color: #8f0075; font-weight: bold;">int</span>, y: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">int</span>:
    return x + y

<span style="color: #005e8b;">another_coll</span> = <span style="color: #8f0075; font-weight: bold;">list</span>(<span style="color: #8f0075; font-weight: bold;">range</span>(10, 20))

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"mapped:"</span>, <span style="color: #8f0075; font-weight: bold;">list</span>(<span style="color: #8f0075; font-weight: bold;">map</span>(add, coll, another_coll)))
</code></pre>
</div>

<pre class="example">
mapped: [10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
</pre>

<p>
Again, we can achieve the same thing with a list comprehension, though this time, we have to use <code>zip</code> in order to create the pairs we want to act on:
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"list-comprehension:"</span>, [add(x, y) for x, y in <span style="color: #8f0075; font-weight: bold;">zip</span>(coll, another_coll)])
</code></pre>
</div>

<pre class="example">
list-comprehension: [10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
</pre>
</div>
</div>
<div id="outline-container-orgf85a300" class="outline-3">
<h3 id="orgf85a300"><code>filter</code></h3>
<div class="outline-text-3" id="text-orgf85a300">
<p>
<code>filter</code>, as the name suggests, lets us filter out values from a collection. Thus, where <code>map</code> lets us transform values in a collection, <code>filter</code> lets us transform the shape of the collection itself without touching the values.
</p>

<p>
It, too, takes two arguments:
</p>
<ul class="org-ul">
<li>A function <code>p</code> that takes a single argument and returns either <code>True</code> or <code>False</code> (this is often called a predicate function, or simply predicate).
It is used to indicate whether or not to <b>keep</b> a specific value.</li>
<li>A collection <code>coll</code> of values.</li>
</ul>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">the predicate that indicates whether we should keep values
</span>def <span style="color: #721045;">p</span>(x: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">bool</span>:
    return x &lt; 10

<span style="color: #005e8b;">coll</span> = <span style="color: #8f0075; font-weight: bold;">list</span>(<span style="color: #8f0075; font-weight: bold;">range</span>(0, 20, 2))

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"original:          "</span>, coll)

<span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">in python, `filter` is also lazy.
</span><span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"filter object:     "</span>, <span style="color: #8f0075; font-weight: bold;">filter</span>(p, coll))
<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"filter:            "</span>, <span style="color: #8f0075; font-weight: bold;">list</span>(<span style="color: #8f0075; font-weight: bold;">filter</span>(p, coll)))

<span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">again, we can achieve the same effect with a list-comprehension
</span><span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"list-comprehension:"</span>, [x for x in coll if p(x)])
</code></pre>
</div>

<pre class="example">
original:           [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
filter object:      &lt;filter object at 0x105404190&gt;
filter:             [0, 2, 4, 6, 8]
list-comprehension: [0, 2, 4, 6, 8]
</pre>

<p>
Because both <code>filter</code> and <code>map</code> are lazy, they can be used well in conjunction, allowing us to only act on the data we actually want to:
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #005e8b;">above_10</span> = <span style="color: #8f0075; font-weight: bold;">filter</span>(lambda x: x &gt; 10, coll)
<span style="color: #005e8b;">multiplied</span> = <span style="color: #8f0075; font-weight: bold;">map</span>(lambda x: x*3, above_10)
<span style="color: #005e8b;">even</span> = <span style="color: #8f0075; font-weight: bold;">filter</span>(lambda x: x % 2 == 0, multiplied)
<span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">use `print` to illustrate that the last `map` doesn't "see" all data
</span><span style="color: #005e8b;">printed</span> = <span style="color: #8f0075; font-weight: bold;">map</span>(<span style="color: #8f0075; font-weight: bold;">print</span>, even)
<span style="color: #8f0075; font-weight: bold;">list</span>(printed)
</code></pre>
</div>

<pre class="example">
36
42
48
54
</pre>

<p>
This also lets us work with extremely large (or even infinite) sequences without any issues (as long as we limit the number of results we want at any point):
</p>

<div class="org-src-container">
<pre class="src src-python"><code>from collections.abc import Iterable

def <span style="color: #721045;">mk_infinite_numbers</span>() -&gt; Iterable[<span style="color: #8f0075; font-weight: bold;">int</span>]:
    <span style="color: #005e8b;">i</span> = 0
    while <span style="color: #0000b0;">True</span>:
        yield i
        <span style="color: #005e8b;">i</span> = i + 1

def <span style="color: #721045;">is_even</span>(x: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">bool</span>:
    return x % 2 == 0

def <span style="color: #721045;">power_of_two</span>(x: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">int</span>:
    return 2 ** x

def <span style="color: #721045;">starts_with_1</span>(x: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">bool</span>:
    return <span style="color: #8f0075; font-weight: bold;">str</span>(x)[0] == <span style="color: #3548cf;">"1"</span>

def <span style="color: #721045;">take</span>[T](n: <span style="color: #8f0075; font-weight: bold;">int</span>, seq: Iterable[T]) -&gt; <span style="color: #8f0075; font-weight: bold;">list</span>[T]:
    <span style="color: #2a5045; font-style: italic;">"""Return the first `n` values from a (possibly infinite) sequence."""</span>
    return [<span style="color: #8f0075; font-weight: bold;">next</span>(seq) for _ in <span style="color: #8f0075; font-weight: bold;">range</span>(n)]

<span style="color: #005e8b;">infinite_numbers</span> = mk_infinite_numbers()
<span style="color: #005e8b;">evens</span> = <span style="color: #8f0075; font-weight: bold;">filter</span>(is_even, infinite_numbers)
<span style="color: #005e8b;">even_powers_of_two</span> = <span style="color: #8f0075; font-weight: bold;">map</span>(power_of_two, evens)
<span style="color: #005e8b;">that_start_with_1</span> = <span style="color: #8f0075; font-weight: bold;">filter</span>(starts_with_1, even_powers_of_two)
<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"first 10:"</span>, take(10, that_start_with_1))
<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"next 10:"</span>, take(10, that_start_with_1))
</code></pre>
</div>

<pre class="example">
first 10: [1, 16, 1024, 16384, 1048576, 16777216, 1073741824, 17179869184, 1099511627776, 17592186044416]
next 10: [1125899906842624, 18014398509481984, 1152921504606846976, 18446744073709551616, 1180591620717411303424, 18889465931478580854784, 1208925819614629174706176, 19342813113834066795298816, 1237940039285380274899124224, 19807040628566084398385987584]
</pre>
</div>
</div>
<div id="outline-container-org8a2f620" class="outline-3">
<h3 id="org8a2f620"><code>reduce</code></h3>
<div class="outline-text-3" id="text-org8a2f620">
<p>
<code>reduce</code> is by for the most difficult to understand (and also most powerful) operation of the three. While the first two capture how me may change the shape and contents of a collection, returning a new collection, <code>reduce</code> allows us to aggregate collections into completely new kinds of data.
</p>

<p>
There are some different definitions of what exactly <code>reduce</code> requires floating around. I'll use the strictest one, often also called <code>fold</code>, which is a function that takes three arguments:
</p>
<ul class="org-ul">
<li>A function <code>f</code> with two arguments: the current aggregated value <code>agg</code> and the new value <code>x</code> to aggregate.</li>
<li>A collection <code>coll</code> of values to aggregate.</li>
<li>The starting value of <code>agg</code>.</li>
</ul>

<p>
I haven't really explained what "aggregating" means in this context. Roughly speaking, this refers to the idea of incrementally updating a value with new information. This may be something as simple as computing a sum, or as complex as managing the current state of a game, given the collection of all player inputs. Strictly speaking, we can even define <code>map</code> and <code>filter</code> in terms of <code>reduce</code>, but I'll leave that as home-work for the reader ;)
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #005e8b;">coll</span> = <span style="color: #8f0075; font-weight: bold;">list</span>(<span style="color: #8f0075; font-weight: bold;">range</span>(1, 11))

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"original:"</span>, coll)

<span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">in python, `reduce` is not in the global namespace.
</span><span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">we have to import it from the builtin `functools` library.
</span>from functools import reduce

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"sum, using reduce:"</span>, reduce(add, coll, 0))
</code></pre>
</div>

<pre class="example">
original: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sum, using reduce: 55
</pre>

<p>
Now this is where trivial alternatives start getting more difficult to come by. Of course, in Python, we could use the <code>sum</code> function for this. But if we didn't have that, we would start using <code>for</code>-loops and variable overriding (something we would want to avoid when writing programs in a functional style).
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"sum, using sum:"</span>, <span style="color: #8f0075; font-weight: bold;">sum</span>(coll))

def <span style="color: #721045;">my_sum</span>(coll: <span style="color: #8f0075; font-weight: bold;">list</span>[<span style="color: #8f0075; font-weight: bold;">int</span>]) -&gt; <span style="color: #8f0075; font-weight: bold;">int</span>:
    <span style="color: #005e8b;">current_sum</span> = 0
    for x in coll:
        <span style="color: #005e8b;">current_sum</span> = current_sum + x

    return current_sum

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"sum, using for-loop:"</span>, my_sum(coll))
</code></pre>
</div>

<pre class="example">
sum, using sum: 55
sum, using for-loop: 55
</pre>

<p>
As for a less trivial example, let's calculate the cumulative sum, using <code>reduce</code> and a <code>for</code>-loop (we could also use <code>itertools.accumulate</code>, but that's effectively just a lazy <code>reduce</code> in this case):
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #7f0000; font-style: italic;"># </span><span style="color: #7f0000; font-style: italic;">we'll just use the previous collection this time
</span>
def <span style="color: #721045;">cumsum_increment</span>(agg: <span style="color: #8f0075; font-weight: bold;">list</span>[<span style="color: #8f0075; font-weight: bold;">int</span>], x: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">list</span>[<span style="color: #8f0075; font-weight: bold;">int</span>]:
    <span style="color: #005e8b;">new_value</span> = agg[-1] + x
    return agg + [new_value]

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"cumsum, using reduce:"</span>, reduce(cumsum_increment, coll, [0]))

def <span style="color: #721045;">cumsum_using_for</span>(coll: <span style="color: #8f0075; font-weight: bold;">list</span>[<span style="color: #8f0075; font-weight: bold;">int</span>]) -&gt; <span style="color: #8f0075; font-weight: bold;">list</span>[<span style="color: #8f0075; font-weight: bold;">int</span>]:
    <span style="color: #005e8b;">cumsums</span> = [0]
    for x in coll:
        <span style="color: #005e8b;">new_value</span> = cumsums[-1] + x
        <span style="color: #005e8b;">cumsums</span> = cumsums + [new_value]

    return cumsums

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"cumsum, using for-loop:"</span>, cumsum_using_for(coll))
</code></pre>
</div>

<pre class="example">
cumsum, using reduce: [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
cumsum, using for-loop: [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
</pre>

<p>
You may noticed some similarities between the two approaches. This is how we could implement <code>reduce</code> using a <code>for</code>-loop:
</p>
<div class="org-src-container">
<pre class="src src-python"><code>def <span style="color: #721045;">my_reduce</span>(f, coll, initial):
    <span style="color: #005e8b;">agg</span> = initial
    for x in coll:
        <span style="color: #005e8b;">agg</span> = f(agg, x)

    return agg
</code></pre>
</div>
</div>
<div id="outline-container-org21f76a4" class="outline-4">
<h4 id="org21f76a4">Bonus: Collecting Results, the General Way</h4>
<div class="outline-text-4" id="text-org21f76a4">
<p>
As a bonus, I'd like to highlight a way we can implement the cumulative sum above by re-using the <code>add</code> function we defined previously. This solution is also more general, allowing us to cumulatively aggregate any kind of operation we may want to do with <code>reduce</code> (e.g. to keep track of the entire history of our game, so we can time-travel).
</p>

<p>
For this, we will be defining a new higher-order function that takes an "aggregator" function like above and turns it into one that returns a list of all of its results. <i>For higher-order functions, type-hints can get a bit much, unfortunately, so I separated the two.</i>
</p>

<div class="org-src-container">
<pre class="src src-python"><code>def <span style="color: #721045;">cumulative_aggregation</span>(f):
    def <span style="color: #721045;">wrapped</span>(agg, x):
        return agg + [f(agg[-1], x)]

    return wrapped
</code></pre>
</div>

<p>
Then, we can use it to calculate our cumulative sum:
</p>

<div class="org-src-container">
<pre class="src src-python"><code><span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"cumsum, using function composition:"</span>, reduce(cumulative_aggregation(add), coll, [0]))
</code></pre>
</div>

<pre class="example">
cumsum, using function composition: [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
</pre>

<p>
Now, we can trivially do the same thing using any other aggregator function, like subtraction:
</p>

<div class="org-src-container">
<pre class="src src-python"><code>def <span style="color: #721045;">minus</span>(x: <span style="color: #8f0075; font-weight: bold;">int</span>, y: <span style="color: #8f0075; font-weight: bold;">int</span>) -&gt; <span style="color: #8f0075; font-weight: bold;">int</span>:
    return x - y

<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"total difference:"</span>, reduce(minus, coll, 0))
<span style="color: #8f0075; font-weight: bold;">print</span>(<span style="color: #3548cf;">"cumulative difference:"</span>, reduce(cumulative_aggregation(minus), coll, [0]))
</code></pre>
</div>

<pre class="example">
total difference: -55
cumulative difference: [0, -1, -3, -6, -10, -15, -21, -28, -36, -45, -55]
</pre>

<p>
As promised, here's the type-hinted version, using Python's new syntax for generic types:
</p>
<div class="org-src-container">
<pre class="src src-python"><code>from collections.abc import Callable

def <span style="color: #721045;">cumulative_aggregation_typed</span>[Agg, X](
    f: Callable[[Agg, X], Agg]
) -&gt; Callable[[<span style="color: #8f0075; font-weight: bold;">list</span>[Agg], X], <span style="color: #8f0075; font-weight: bold;">list</span>[Agg]]:
    def <span style="color: #721045;">wrapped</span>(agg: <span style="color: #8f0075; font-weight: bold;">list</span>[Agg], x: X) -&gt; <span style="color: #8f0075; font-weight: bold;">list</span>[Agg]:
        return agg + [f(agg[-1], x)]

    return wrapped
</code></pre>
</div>
</div>
</div>
</div>
</div>]]></content><author><name>Josefine Katzke</name><email>jokatzke@fastmail.com</email></author><category term="Programming" /><summary type="html"><![CDATA[For this series of blog posts, I want to clear up some common misconceptions I've seen people make whilst they are first learning about functional programming, and the "map, filter, reduce pattern" in particular. These basically boil down to "I can achieve the same thing with a for-loop / list-comprehension".]]></summary></entry></feed>