{
    "version": "https://jsonfeed.org/version/1",
    "title": "michalwa | Michał Wawrzynowicz",
    "description": "Hi there! This is my personal website and blog where I hope to share personal projects and thoughts on programming, music, philosophy, and other interesting things.",
    "home_page_url": "https://michalwa.github.io/",
    "feed_url": "https://michalwa.github.io/feed.json",
    "author": {
        "name": "Michał Wawrzynowicz <michalwa2003@gmail.com>"},
    "items": [
      {
            "id": "https://michalwa.github.io/2026/07/13/ruby-learnings",
            "url": "https://michalwa.github.io/2026/07/13/ruby-learnings.html",
            "title": "Lessons Learned After 4+ Years of Ruby on Rails",
            "content_html": "<h1 id=\"lessons-learned-after-4-years-of-ruby-on-rails\">Lessons Learned After 4+ Years of Ruby on Rails</h1>\n\n<h2 id=\"introduction\">Introduction</h2>\n\n<p>Until recently, I was a professional Ruby developer for over 4 years. Before that, I had used the language occasionally for small personal projects or learning. To be honest, it’s never been quite up my alley. But when I was starting out, I assumed that my feelings about technologies should be put aside and that I should rather focus on building things. I still believe that to a certain extent, and I’m not saying goodbye to Ruby for good, but my opinions have matured over the years into more and more certain convictions.</p>\n\n<p>I actually got into Ruby quite late, its popularity already being long in decline. I mostly worked on projects which had been started years before. <em>Rails</em> projects specifically. I was never really interested in participating in the community aside from what I needed to do my job. The philosophy behind the language was also never really anything to write home about for me.</p>\n\n<p>I do think Ruby is a good scripting language. I think it’s my favorite among those, actually. The one thing I can honestly praise it for the most is how it fully takes advantage of being an interpreted language. It’s unlikely you’ll find object-oriented runtime metaprogramming/reflection this poweful anywhere else. You can totally bend this language to your will. This unfortunately ends up being one of the nails in its coffin when it hits production.</p>\n\n<p>It’s also worth mentioning that the language itself as a tool is actually not to blame for many of the shortcomings I’ll describe here. Some of the criticism is perhaps more appropriately aimed at Rails specifically, rather than Ruby. But I think the nature of my complaints still demonstrates how, ultimately, the <em>practical Ruby experience</em> involves patterns that are problematic in the long run. And I think, in this sense, the way a language is used—all the various conventions and philosophies around it, as well as its major frameworks and libraries—end up largely defining what the language actually <em>is</em>.</p>\n\n<p>I will also clarify that I’m intentionally not accounting for the historical context of Ruby’s development. I am simply assuming the perspective of a modern-day engineer with reasonable expectations for an actively maintained programming language and ecosystem.</p>\n\n<h2 id=\"pain\">Pain</h2>\n\n<p>I want to take <a href=\"https://github.com/rails/rails/tree/main/activerecord\">ActiveRecord</a> as an example, because I think it really shows the worst sides of this language and ecosystem. It’s an ORM and it makes <strong>heavy</strong> use of dynamic method generation. One particular feature it provides is implicit generation of scope methods for enum attributes. Say you have the following model:</p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">Post</span> <span class=\"o\">&lt;</span> <span class=\"no\">ActiveRecord</span><span class=\"o\">::</span><span class=\"no\">Base</span>\n  <span class=\"n\">enum</span> <span class=\"ss\">:status</span><span class=\"p\">,</span> <span class=\"sx\">%i(draft published)</span>\n<span class=\"k\">end</span>\n</code></pre></div></div>\n\n<p>This will implicitly generate the following class methods:</p>\n<ul>\n  <li><code class=\"language-plaintext highlighter-rouge\">Post.draft</code></li>\n  <li><code class=\"language-plaintext highlighter-rouge\">Post.not_draft</code></li>\n  <li><code class=\"language-plaintext highlighter-rouge\">Post.published</code></li>\n  <li><code class=\"language-plaintext highlighter-rouge\">Post.not_published</code></li>\n</ul>\n\n<p>All of which return an appropriate collection/relation object.</p>\n\n<p>This is all fun and games (<em>is it really?</em>) until you need to debug something and you see such a method called out in the wild. Imagine seeing the following code:</p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">filtered_posts</span><span class=\"p\">.</span><span class=\"nf\">not_published</span>\n</code></pre></div></div>\n\n<p>Imagine inspecting the resulting SQL and finding that it performs some filtering you never intended.</p>\n\n<p>Now to even know where to look for the definition you need to either assume that <code class=\"language-plaintext highlighter-rouge\">filtered_posts</code> corresponds to the <code class=\"language-plaintext highlighter-rouge\">Post</code> class purely by mentally parsing and matching the name, or navigate to the definition of <code class=\"language-plaintext highlighter-rouge\">filtered_posts</code> to see what it actually returns.</p>\n\n<p>You could be lucky and find <code class=\"language-plaintext highlighter-rouge\">filtered_posts</code> referencing the <code class=\"language-plaintext highlighter-rouge\">Post</code> class, applying some scoping, and returning the resulting relation… or you might find something like this:</p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">filtered_posts</span>\n  <span class=\"k\">if</span> <span class=\"n\">some_condition</span>\n    <span class=\"no\">Post</span><span class=\"p\">.</span><span class=\"nf\">filter</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">)</span>\n  <span class=\"k\">else</span>\n    <span class=\"no\">Article</span><span class=\"p\">.</span><span class=\"nf\">filter</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">)</span>\n  <span class=\"k\">end</span>\n<span class=\"k\">end</span>\n</code></pre></div></div>\n\n<p>Now the code analysis path diverges. This pattern may continue indefinitely.</p>\n\n<p>When you do miraculously arrive at the <code class=\"language-plaintext highlighter-rouge\">Post</code> class, what do you do? Do you search for the definition of a method called <code class=\"language-plaintext highlighter-rouge\">not_published</code>? No luck. What you have to do is <strong>know</strong> the format of the identifier that ActiveRecord may generate for a particular enum value and either <strong>know</strong> that such an enum exists or blindly search for <code class=\"language-plaintext highlighter-rouge\">published</code> in hopes of finding it.</p>\n\n<p>This is a contrived example, so I don’t know what the ultimate issue could be here. Perhaps you find that <code class=\"language-plaintext highlighter-rouge\">published</code> is actually a variant on a different attribute than the one you intended to filter by… You get the picture.</p>\n\n<p>Mind you, Ruby’s design philosophy supposedly maximizes <em>programmer joy</em>. I will proudly admit I got very good at this kind of stuff over the years, but it became increasingly difficult to find any joy in it.</p>\n\n<h2 id=\"greppability-disaster\"><em>Greppability</em> disaster</h2>\n\n<p><a href=\"https://morizbuesing.com/blog/greppability-code-metric\">Greppability</a> is a metric of how well a codebase can be navigated using basic text search (or <code class=\"language-plaintext highlighter-rouge\">grep</code>). It’s effectively inversely proportional to how often you will find yourself lost in the ways I mentioned earlier.</p>\n\n<p>There are several stages to what I would simply call a kind of obfuscation, but let’s call it <em>anti-greppability</em> to be specific:</p>\n\n<ol>\n  <li>Symbols are generated by concatenating user-provided symbols with pre-determined affixes. Take the example from above, <code class=\"language-plaintext highlighter-rouge\">published</code> gets transformed into <code class=\"language-plaintext highlighter-rouge\">not_published</code>. This is not even that disruptive once you get used to it, because the original identifier is still contained within the generated one, and you can sort of get by in the ways I mentioned earlier.</li>\n  <li>Symbols are generated by inflecting user-provided symbols. This is a bizarre feature of Rails, where you call a model <code class=\"language-plaintext highlighter-rouge\">Product</code> and the table name implicitly defaults to <code class=\"language-plaintext highlighter-rouge\">products</code>. This wouldn’t be significant if not for the fact that special cases like <code class=\"language-plaintext highlighter-rouge\">Child</code> are correctly inflected and become <code class=\"language-plaintext highlighter-rouge\">children</code>, same for <code class=\"language-plaintext highlighter-rouge\">Person</code> to <code class=\"language-plaintext highlighter-rouge\">people</code>, etc. You have to ask yourself: Why? Would it really be so bad to require explicitly specifying the table name?</li>\n  <li>Symbols are generated completely dynamically. If you’re coming from another language you might think this is a joke, but ActiveRecord, as many other Ruby libraries, generates methods based on a completely dynamic, statically <em>opaque</em> source, namely the database schema. And one which is only very loosely ensured to be uniform across environments. So the meaning of your code may effectively change depending on where it runs…</li>\n</ol>\n\n<p>Keep in mind, these mechanisms are used in Rails seemingly just because they <em>can</em> be used. I see no way in which they’re vital to the design of the framework. There are many other frameworks and libraries which make use of such patterns. The mere fact that it’s possible to do this in this language seems to encourage it among framework designers.</p>\n\n<p>Most of this is also configurable, of course. You could very well override some of the dynamically generated symbols with explicit configuration if you wanted to. However, Rails discourages this and nobody ever does this.</p>\n\n<p>Rails has a problematic interpretation of <em>convention over configuration</em> which falls apart as soon as you realize that convention is not always sufficient and configuration is still possible, which in turn makes convention unreliable. Without very rigorous code review you will always end up with half your models relying on convention and half configured with overrides or even your own metaprogramming… You end up spending hours figuring out “<em>Is this something generated by Rails or should I look for the definition in our code?</em>” So in a way the philosophy actually ends up nurturing the confusion it’s advertised to solve.</p>\n\n<p>To me, all of this paints a picture of a completely impractical, toy-like technology. Fun to look at, maybe, but impractical.</p>\n\n<h2 id=\"impossible-tooling\">Impossible tooling</h2>\n\n<p>LSPs struggle significantly against this. Not only are symbols mismatched textually, there is not even a way to trace their origin programmatically. There are only a <em>few</em> reasonable LSPs for Ruby, none of which I found very good.</p>\n\n<p>In a sensible metaprogramming system you should generally provide some way to map parts of the generated code to where they originate—either in the source code of the <em>generator</em>, e.g. a macro, or the user call site where that generator is invoked. Take Rust’s macros for example. There is the concept of a <a href=\"https://doc.rust-lang.org/proc_macro/struct.Span.html\">span</a> which specifically refers to a region of source code, not just a string. When you use a span in the expansion of a macro, language tooling can later trace the resulting definition to the original span.</p>\n\n<p>It would be very helpful, going back to my example, if <code class=\"language-plaintext highlighter-rouge\">not_published</code> was somehow statically accessible and referred to the original location of the <code class=\"language-plaintext highlighter-rouge\">published</code> symbol. It would at least allow <em>go to definition</em> functionality to some extent. But Ruby has no such capabilities. You can inspect certain properties of dynamically generated code, like <code class=\"language-plaintext highlighter-rouge\">Method#source_location</code>, but this is hardly sufficient. The larger problem is that all such information is only available at <strong>runtime</strong>. There is often just no way of statically deducing what the meaning of your code is.</p>\n\n<p>Even <em>Sorbet</em> (a static type checker I’ll come back to later on) has a <a href=\"https://github.com/Shopify/tapioca\">generator</a> for type shims which resorts to simply <strong>loading the Rails application</strong> in development and inspecting pre-defined sets of classes and symbols and inferring their types, more or less intelligently. Don’t get me wrong, I find the efforts of Sorbet and its tooling commendable, given the inherent restrictions of the language they aim to support. But this is a mess.</p>\n\n<p>The most helpful LSP-like editor plugins for Rails I’ve seen were usually implemented for specific layers of the framework, with basically hardcoded rules for symbol generation.</p>\n\n<p>Some people recommended <a href=\"https://www.jetbrains.com/ruby\">RubyMine</a> to me. Supposedly it would have some superior LSP functionality. I’ve never used it, but I don’t believe it can be significantly better because of the inherent nature of these issues, and it’s a big piece of software that I don’t think should be required to comfortably use a programming language.</p>\n\n<h2 id=\"dynamic-typing-is-expensive\">Dynamic typing is expensive</h2>\n\n<p>This is something I feel every less experienced engineer has to learn the hard way sooner or later. Dynamic typing makes sense so long as you can infer your runtime types from variable names and context alone. As soon as you are not the only person holding the entire codebase and domain context in your head, you just can’t be sure what types you’re dealing with anymore and end up spending hours debugging runtime type errors. It gets increasingly worse as boundaries arise between components/modules, with potentially separate teams working on either side.</p>\n\n<p>I guess you can also argue that it’s always a trade-off: You either get a lot of compile-time errors with static typing, or a lot of runtime errors with dynamic typing. But I don’t think anyone argues that the latter is somehow preferable in large production code.</p>\n\n<p><a href=\"https://sorbet.org\">Sorbet</a> is a good attempt at putting a band-aid on existing codebases in a gradual and non-invasive way. It’s quite early in development (at least up until a few months ago) and the type system makes some unfortunate <a href=\"https://sorbet.org/docs/stdlib-generics#standard-library-generics-and-variance\">trade-offs</a> borrowed from TypeScript, which made me sad, but overall it’s worth checking out. As someone experienced with both statically typed languages and Ruby I had little to no issue gradually introducing Sorbet into a large production codebase. One thing I found it solves really nicely is missing <code class=\"language-plaintext highlighter-rouge\">nil</code> checks. It also has reasonably powerful runtime validation (similar to <code class=\"language-plaintext highlighter-rouge\">pydantic</code>), so it was a huge help in defining DTOs across component boundaries.</p>\n\n<h2 id=\"going-forward\">Going forward</h2>\n\n<p>Getting serious stuff done in this technology requires intense discipline, contrary to the somewhat naive belief that technologies with fewer compile-time checks allow building things faster. Yes, you might roll out an MVP with <em>less code typed</em>, but the time it takes you to develop it any further will grow exponentially.</p>\n\n<p>Fortunately, I think this perspective is increasingly common in the modern day. Again, Ruby’s glory days have probably passed a long time ago, together with other similar technologies. I think Python today usually has type hints and you will get reasonable static checks and not a lot of runtime macro voodoo.</p>\n\n<p>Were I to pick up a Ruby codebase today, I would be <strong>extremely</strong> mindful of the level of code style standards it employs, to make sure it at least tries not to make these issues worse.</p>",
            "summary": "Lessons Learned After 4+ Years of Ruby on Rails",
            "date_published": "2026-07-13T00:00:00+00:00",
            "date_modified": "2026-07-13T00:00:00+00:00",
            "author": {
              "name": "Michał Wawrzynowicz <michalwa2003@gmail.com>"},
            "tags": []},{
            "id": "https://michalwa.github.io/2026/07/10/designing-a-database",
            "url": "https://michalwa.github.io/2026/07/10/designing-a-database.html",
            "title": "Designing a Database for a Prolog-based PKM Application",
            "content_html": "<h1 id=\"designing-a-database-for-a-prolog-based-pkm-application\">Designing a Database for a Prolog-based PKM Application</h1>\n\n<p><img src=\"https://raw.githubusercontent.com/michalwa/factbook/main/screenshot-dark.png\" alt=\"factbook screenshot\" /></p>\n\n<h2 id=\"introduction\">Introduction</h2>\n\n<p><a href=\"https://github.com/michalwa/factbook\">factbook</a> is a minimalist PKM app I’ve been working on for the past few months as a hobby project. It’s written in <a href=\"https://rust-lang.org\">Rust</a> using <a href=\"https://tauri.app\">Tauri</a> and <a href=\"https://www.solidjs.com\">Solid.js</a> for the UI. Its workflow resembles Zettelkasten—small, atomic entries and heavy use of tags which emerge organically. What makes it unique is that all organization logic is entirely delegated to a user-facing scripting layer built on top of Prolog.</p>\n\n<p><a href=\"https://www.metalevel.at/prolog\">Prolog</a> is a logic programming language. Aside from being 100% Turing-complete, logic problems and relational search are where it really shines. The interpreter is pretty much a first-order logic solver and has built-in concepts of <em>choice points</em>, <em>backtracking</em>, etc. The language also relies heavily on the concept of <em>unification</em>, which can be thought of as a form of <em>pattern matching</em>, which makes it really easy to express very complex nested relations within data structures. It has many similarities to the LISP family—although it doesn’t use S-expressions, its design very much follows the spirit of <em>code as data</em>.</p>\n\n<p>If you’re unfamiliar with logic programming and are open to exploring new, foreign paradigms of thinking I encourage you to investigate further. Prolog has defined a completely new perspective on many concepts in both programming and mathematics for me. I think people usually get introduced to Prolog in an academic setting, but I just stumbled upon it one day in my spare time and got hooked.</p>\n\n<p>The way factbook attempts to take advantage of this technology is by allowing users to tag their entries with arbitrary Prolog <em>terms</em>. In short, this means instead of simple tags like <code class=\"language-plaintext highlighter-rouge\">#book</code> or <code class=\"language-plaintext highlighter-rouge\">#task</code>, you can write <code class=\"language-plaintext highlighter-rouge\">@book([read(false), rating(3)])</code>, meaning you can pretty much embed arbitrary data structures into your notes, which I think is pretty cool. Prolog’s minimalist syntax makes it quite clean and readable too, as opposed to using something obvious like JSON.</p>\n\n<p>The way you can then reason about these tags is by means of queries (<em>views</em>), which are made to look more or less like a subset of Prolog, giving you pretty much the entire language at your disposal, with some domain-specific/quality-of-life features added on top.</p>\n\n<p>I would often find myself using PKM/note-taking apps in elaborate, over-engineered ways. And sometimes they just weren’t enough or didn’t feel right, which is how I came up with the idea once I got <a href=\"https://github.com/michalwa/aoc-2025\">more or less comfortable using Prolog</a>. It just seemed like a fun project to work on potentially leading to an actual usable product, or at least usable by me :) I won’t pretend like the main reason I’m building this thing isn’t just because it’s fun to think about.</p>\n\n<p>I’m not aware of any similar attempts, although similar tools in general do exist. Most noteworthy probably being Emacs Org Mode, which I’m planning to study at some point.</p>\n\n<h2 id=\"the-problem\">The problem</h2>\n\n<p>Let me first show you an example of how the app is designed to work. Here’s what a <em>journal</em> in factbook might look like. Entries are numbered for later reference; in the actual app they are annotated with timestamps.</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>0 | @todo(false) this is an uncompleted task\n1 | @todo(true) this is a completed task\n2 | this is a @todo(false) in the context of @work\n3 | this is a @thought where I want to @cite(\"Einstein\", 1905)\n4 | another @thought\n5 | some research @cite(\"Turing\", 1936)\n</code></pre></div></div>\n\n<p>Now, say you want to list all uncompleted tasks. You would simply use the query: <code class=\"language-plaintext highlighter-rouge\">@todo(false)</code>. Want to see all tasks regardless of completion? Put an unbound variable as the argument: <code class=\"language-plaintext highlighter-rouge\">@todo(_)</code>. Of course, there’s also boolean operators: conjunction (<code class=\"language-plaintext highlighter-rouge\">,</code>) and disjunction (<code class=\"language-plaintext highlighter-rouge\">;</code>), so you can say things like <code class=\"language-plaintext highlighter-rouge\">@todo(false); @thought, @cite(_, _)</code> (<em>uncompleted tasks or thoughts which cite something</em>).</p>\n\n<p>There’s also other predicates which don’t refer to tags. For instance, you can reason about the creation times of entries (<code class=\"language-plaintext highlighter-rouge\">created(_)</code>); or execute completely arbitrary Prolog code (<code class=\"language-plaintext highlighter-rouge\">{ ... }</code>). This naturally includes accessing the file system or making web requests, not that you should necessarily do those things.</p>\n\n<p>Evaluating these kinds of queries in Prolog on its own is trivial. However, it requires that the associations between entries and their tags are available to the Prolog runtime. This is where the engineering challenge begins. How can we store this data in a way that is maintainable, efficient to mutate, and available to the Prolog runtime?</p>\n\n<h2 id=\"the-easy-solution\">The easy solution</h2>\n\n<p>Prolog maintains a database which you normally populate with a program ahead of time and subsequently query (or evaluate <em>goals</em>). Let’s see what our example entry database could look like written as a Prolog program:</p>\n\n<div class=\"language-prolog highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">0</span><span class=\"p\">,</span> <span class=\"ss\">todo</span><span class=\"p\">(</span><span class=\"ss\">false</span><span class=\"p\">)).</span>\n<span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">1</span><span class=\"p\">,</span> <span class=\"ss\">todo</span><span class=\"p\">(</span><span class=\"ss\">true</span><span class=\"p\">)).</span>\n<span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">2</span><span class=\"p\">,</span> <span class=\"ss\">todo</span><span class=\"p\">(</span><span class=\"ss\">false</span><span class=\"p\">)).</span>\n<span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">2</span><span class=\"p\">,</span> <span class=\"ss\">work</span><span class=\"p\">).</span>\n<span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">3</span><span class=\"p\">,</span> <span class=\"ss\">thought</span><span class=\"p\">).</span>\n<span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">3</span><span class=\"p\">,</span> <span class=\"ss\">cite</span><span class=\"p\">(</span><span class=\"s2\">\"Einstein\"</span><span class=\"p\">,</span> <span class=\"m\">1905</span><span class=\"p\">)).</span>\n<span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">4</span><span class=\"p\">,</span> <span class=\"ss\">thought</span><span class=\"p\">).</span>\n<span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">5</span><span class=\"p\">,</span> <span class=\"ss\">cite</span><span class=\"p\">(</span><span class=\"s2\">\"Turing\"</span><span class=\"p\">,</span> <span class=\"m\">1936</span><span class=\"p\">)).</span>\n</code></pre></div></div>\n\n<p>I’m making the rather obvious assumption here that we’re interested in storing <em>parsed tags</em> and not <em>entry content</em> to avoid having to parse each entry during search. I leave storage of entry content as a separate question for now, though it would definitely be nice to arrive at a solution which also accounts for it.</p>\n\n<p>The above list of <em>facts</em> defines the predicate <code class=\"language-plaintext highlighter-rouge\">entry_tag/2</code> (standard notation, <code class=\"language-plaintext highlighter-rouge\">/2</code> indicates arity) which associates an entry ID with a tag included in the entry. To then retrieve IDs of entries containing the <code class=\"language-plaintext highlighter-rouge\">todo/1</code> tag, for example, we could use the following Prolog query: <code class=\"language-plaintext highlighter-rouge\">?- entry_tag(E, todo(_))</code>.</p>\n\n<p>But say we change the content of entry <code class=\"language-plaintext highlighter-rouge\">0</code> from “<em>@todo(false) this is an uncompleted task</em>” to “<em>@todo(true) this is a completed task</em>”. How can we update the Prolog database accordingly? Nuking the entire runtime and initializing a new one with a new state after every change won’t do. So what else can we do?</p>\n\n<p>Well, it turns out you can declare facts and rules at “query time” using <code class=\"language-plaintext highlighter-rouge\">asserta/1</code> and <code class=\"language-plaintext highlighter-rouge\">assertz/1</code>. These are special built-in predicates, which you can query just like other predicates, including as part of rules, but have the side effects of either prepending or appending facts or rules to the database. There are also a bunch of related predicates for retracting clauses from the database.</p>\n\n<p>Going back to our example.</p>\n\n<p>To be safe we would probably need to first retract all facts about that entry:</p>\n\n<div class=\"language-prolog highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"ss\">retract</span><span class=\"p\">(</span><span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">0</span><span class=\"p\">,</span> <span class=\"nv\">_</span><span class=\"p\">)).</span>\n</code></pre></div></div>\n\n<p>And subsequently assert the facts corresponding to each of the new tags:</p>\n\n<div class=\"language-prolog highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"ss\">assertz</span><span class=\"p\">(</span><span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"m\">0</span><span class=\"p\">,</span> <span class=\"ss\">todo</span><span class=\"p\">(</span><span class=\"ss\">true</span><span class=\"p\">))).</span>\n</code></pre></div></div>\n\n<p>If the entry got deleted as a whole, we would only do the retraction.</p>\n\n<p>At startup, we would of course have to parse all entries, serialize the ID-tag pairs into Prolog clauses and pass them to the interpreter.</p>\n\n<p>This might seem like a perfect solution at first glance and, to be fair, I have not honestly tested this and verified its performance. It could turn out that for small sets of entries it’s completely viable. However, I immediately got a strong gut feeling that this would not be the way to go.</p>\n\n<p>In a professional setting, I would probably go through the effort of actual prototyping to have concrete measurements to base my judgment on. In my personal project I get to skip that part.</p>\n\n<p>Let me try to explain why I felt this way:</p>\n\n<p>Having to manage state through the Prolog interpreter would be cumbersome. All mutations would have to go through this layer instead of being written in plain Rust, or even worse, Prolog would have to be kept in sync with other external data structures.</p>\n\n<p>In any case, it’s questionable architecturally. Relying on the Prolog database gives limited to no control over the way data is stored. It wouldn’t be possible to take advantage of convenient Rust crates / data structures to implement the storage and any potential improvements or additions would be dependent on the specifics of Prolog or even its particular implementation.</p>\n\n<p>I make actual performance judgments warily, having skipped actual benchmarking, but I reckon the universal nature of the dynamic database would ultimately prove worse than a custom native implementation. Prolog interpreters do provide certain ways to improve performance from the level of user code, but still possess certain inherent constraints.</p>\n\n<h2 id=\"setup\">Setup</h2>\n\n<p>The way you interface with the Prolog interpreter is split into two aspects:</p>\n\n<p><strong>Constructing terms:</strong> This is done with functions like <code class=\"language-plaintext highlighter-rouge\">PL_put_atom</code> or <code class=\"language-plaintext highlighter-rouge\">PL_put_functor</code>, which you call “bottom-up” to construct your term trees. In my <a href=\"https://github.com/michalwa/factbook/tree/main/factbook-swipl\">Rust wrapper</a> around SWI-Prolog I have a macro which allows me to do this more conveniently. You can also simply delegate parsing to the interpreter using <code class=\"language-plaintext highlighter-rouge\">PL_put_term_from_chars</code>, with some limitations. The one crucial use of this is for constructing the query term (<em>goal</em>).</p>\n\n<p><strong>Calling Rust from Prolog:</strong> This is where the magic happens. You can register <em>foreign predicates</em> in the Prolog runtime pointing to native implementations that will be called to obtain solutions. The implementations are free to emulate all of the properties of regular predicates.</p>\n\n<p>There are generally several <em>modes</em> of predicates, the two which are relevant here being <em>semi-deterministic</em> and <em>non-deterministic</em>. The former are allowed either to fail or to succeed exactly once (producing a single solution). The latter may succeed an arbitrary number of times, producing alternative solutions. Our earlier definition of <code class=\"language-plaintext highlighter-rouge\">entry_tag/2</code> is an example of a non-deterministic predicate, since for certain arguments (even fully instantiated, as entries may contain duplicate tags) it may yield more than one solution.</p>\n\n<p>In my wrapper, I have two traits describing this (simplified):</p>\n\n<div class=\"language-rust highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">trait</span> <span class=\"n\">Semidet</span> <span class=\"p\">{</span>\n    <span class=\"k\">fn</span> <span class=\"nf\">call</span><span class=\"p\">(</span><span class=\"n\">args</span><span class=\"p\">:</span> <span class=\"o\">&amp;</span><span class=\"p\">[</span><span class=\"n\">Term</span><span class=\"p\">])</span> <span class=\"k\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"k\">trait</span> <span class=\"n\">Nondet</span> <span class=\"p\">{</span>\n    <span class=\"k\">fn</span> <span class=\"nf\">init</span><span class=\"p\">()</span> <span class=\"k\">-&gt;</span> <span class=\"k\">Self</span><span class=\"p\">;</span>\n    <span class=\"k\">fn</span> <span class=\"nf\">next</span><span class=\"p\">(</span><span class=\"o\">&amp;</span><span class=\"k\">mut</span> <span class=\"k\">self</span><span class=\"p\">,</span> <span class=\"n\">args</span><span class=\"p\">:</span> <span class=\"o\">&amp;</span><span class=\"p\">[</span><span class=\"n\">Term</span><span class=\"p\">])</span> <span class=\"k\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>As you can see, non-deterministic predicates are allowed to construct a state and mutate it upon each invocation during search.</p>\n\n<p>Terms, on the other hand, may contain arbitrary blobs of bytes, meaning we can actually embed references to some native state into the goal term and pass it down to our foreign predicate implementation. The wrapper defines a bunch of blob types which allow doing this with proper Rust borrow checking.</p>\n\n<p>The predicate implementation may then construct its internal state which can hold those references and allow each subsequent invocation to read our native application state.</p>\n\n<p>Altogether, this lets us construct the core infrastructure that will allow us to implement any kind of search natively in Rust and make it available to Prolog.</p>\n\n<p><img src=\"/assets/images/factbook-search-arch.svg\" alt=\"architecture diagram\" /></p>\n\n<h2 id=\"so-far-so-good\">So far so good</h2>\n\n<p>Okay, but we are left with actually implementing the storage and search.</p>\n\n<p>Let’s first assume our native predicate will have a similar signature to what we initially sketched out, meaning: <code class=\"language-plaintext highlighter-rouge\">entry_tag(Id, Tag)</code>. Since we know we’ll have to pass external context, let’s also include that as an argument: <code class=\"language-plaintext highlighter-rouge\">entry_tag(Ctx, Id, Tag)</code>. This context will include a reference to the actual storage of entires, as well as any indexes or caches we decide to maintain for support.</p>\n\n<p>Using my Rust wrapper crate, you can specify this as follows:</p>\n\n<div class=\"language-rust highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nd\">#[derive(ScopedBlobData)]</span>\n<span class=\"k\">struct</span> <span class=\"n\">Context</span><span class=\"o\">&lt;</span><span class=\"nv\">'a</span><span class=\"o\">&gt;</span><span class=\"p\">(</span><span class=\"o\">&amp;</span><span class=\"nv\">'a</span> <span class=\"cm\">/* some internal storage type */</span><span class=\"p\">);</span>\n\n<span class=\"nd\">#[predicate(entry_tag(ctx,</span> <span class=\"nd\">id,</span> <span class=\"nd\">tag)</span> <span class=\"nd\">nondet)]</span>\n<span class=\"k\">struct</span> <span class=\"n\">EntryTag</span> <span class=\"p\">{</span> <span class=\"cm\">/* ... */</span> <span class=\"p\">}</span>\n\n<span class=\"k\">impl</span> <span class=\"n\">Nondet</span> <span class=\"k\">for</span> <span class=\"n\">EntryTag</span> <span class=\"p\">{</span>\n    <span class=\"cm\">/* ... */</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>When the user submits a query, we want to:</p>\n<ol>\n  <li>Wrap references to application state in a <code class=\"language-plaintext highlighter-rouge\">Context</code> blob.</li>\n  <li>Construct the query term (goal) with the following arguments:\n    <ul>\n      <li>the newly constructed <code class=\"language-plaintext highlighter-rouge\">Context</code> blob,</li>\n      <li>the user’s query,</li>\n      <li>an unbound variable which will be unified with matching entry IDs.</li>\n    </ul>\n  </li>\n  <li>Evaluate the query and collect the resulting entry IDs.</li>\n</ol>\n\n<p>There is still the question of translating the custom <em>view language</em> into a Prolog query. In factbook this is implemented on the Prolog side as a regular predicate taking the <em>view</em> as an argument and invoking the foreign predicate when necessary.</p>\n\n<p>Let’s now think about some example invocations of the predicate and what they should return:</p>\n\n<div class=\"language-prolog highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"nv\">_</span><span class=\"p\">,</span> <span class=\"nv\">E</span><span class=\"p\">,</span> <span class=\"ss\">thought</span><span class=\"p\">).</span> <span class=\"c1\">% context blob is omitted here</span>\n</code></pre></div></div>\n\n<p>This should yield solutions for <code class=\"language-plaintext highlighter-rouge\">E</code> where the entry contains the tag <code class=\"language-plaintext highlighter-rouge\">@thought</code>. In our example, that would be <code class=\"language-plaintext highlighter-rouge\">E = 3</code> and <code class=\"language-plaintext highlighter-rouge\">E = 4</code>.</p>\n\n<p>Given a list of entries and tags, you can imagine having a simple inverted index by tag names to efficiently traverse entries sharing the same tag. This gives us our first clue for what the implementation should look like.</p>\n\n<div class=\"language-rust highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">struct</span> <span class=\"n\">Database</span> <span class=\"p\">{</span>\n    <span class=\"n\">entries</span><span class=\"p\">:</span> <span class=\"n\">BTreeMap</span><span class=\"o\">&lt;</span><span class=\"n\">EntryId</span><span class=\"p\">,</span> <span class=\"n\">Entry</span><span class=\"o\">&gt;</span><span class=\"p\">,</span>\n    <span class=\"n\">tags</span><span class=\"p\">:</span> <span class=\"nb\">Vec</span><span class=\"o\">&lt;</span><span class=\"p\">(</span><span class=\"n\">EntryId</span><span class=\"p\">,</span> <span class=\"n\">Tag</span><span class=\"p\">,</span> <span class=\"n\">TagData</span><span class=\"p\">)</span><span class=\"o\">&gt;</span><span class=\"p\">,</span>\n    <span class=\"n\">tag_indices</span><span class=\"p\">:</span> <span class=\"n\">BTreeMap</span><span class=\"o\">&lt;</span><span class=\"n\">Tag</span><span class=\"p\">,</span> <span class=\"nb\">Vec</span><span class=\"o\">&lt;</span><span class=\"nb\">usize</span><span class=\"o\">&gt;&gt;</span><span class=\"p\">,</span>\n<span class=\"p\">}</span>\n\n<span class=\"nd\">#[derive(ScopedBlobData)]</span>\n<span class=\"k\">struct</span> <span class=\"n\">Context</span><span class=\"o\">&lt;</span><span class=\"nv\">'a</span><span class=\"o\">&gt;</span><span class=\"p\">(</span><span class=\"o\">&amp;</span><span class=\"nv\">'a</span> <span class=\"n\">Database</span><span class=\"p\">);</span>\n\n<span class=\"nd\">#[predicate(entry_tag(ctx,</span> <span class=\"nd\">id,</span> <span class=\"nd\">tag)</span> <span class=\"nd\">nondet)]</span>\n<span class=\"k\">struct</span> <span class=\"n\">EntryTag</span> <span class=\"p\">{</span>\n    <span class=\"n\">iter</span><span class=\"p\">:</span> <span class=\"nb\">Option</span><span class=\"o\">&lt;</span><span class=\"nb\">Box</span><span class=\"o\">&lt;</span><span class=\"k\">dyn</span> <span class=\"nb\">Iterator</span><span class=\"o\">&lt;</span><span class=\"n\">Item</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">EntryId</span><span class=\"p\">,</span> <span class=\"n\">TagData</span><span class=\"p\">)</span><span class=\"o\">&gt;&gt;&gt;</span><span class=\"p\">,</span>\n<span class=\"p\">}</span>\n\n<span class=\"k\">impl</span> <span class=\"n\">Nondet</span> <span class=\"k\">for</span> <span class=\"n\">EntryTag</span> <span class=\"p\">{</span>\n    <span class=\"k\">fn</span> <span class=\"nf\">init</span><span class=\"p\">()</span> <span class=\"k\">-&gt;</span> <span class=\"k\">Self</span> <span class=\"p\">{</span>\n        <span class=\"k\">Self</span> <span class=\"p\">{</span> <span class=\"n\">iter</span><span class=\"p\">:</span> <span class=\"nb\">None</span> <span class=\"p\">}</span>\n    <span class=\"p\">}</span>\n\n    <span class=\"k\">fn</span> <span class=\"nf\">call</span><span class=\"p\">(</span><span class=\"o\">&amp;</span><span class=\"k\">mut</span> <span class=\"k\">self</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">ctx</span><span class=\"p\">,</span> <span class=\"n\">id</span><span class=\"p\">,</span> <span class=\"n\">tag</span><span class=\"p\">]:</span> <span class=\"o\">&amp;</span><span class=\"p\">[</span><span class=\"n\">Term</span><span class=\"p\">])</span> <span class=\"k\">-&gt;</span> <span class=\"nb\">bool</span> <span class=\"p\">{</span>\n        <span class=\"c1\">// extract `Context` from `ctx`</span>\n\n        <span class=\"k\">if</span> <span class=\"k\">self</span><span class=\"py\">.iter</span><span class=\"nf\">.is_none</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n            <span class=\"c1\">// initialize iterator based on `tag`</span>\n        <span class=\"p\">}</span>\n\n        <span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"n\">entry</span><span class=\"p\">,</span> <span class=\"n\">tag</span><span class=\"p\">)</span> <span class=\"k\">in</span> <span class=\"k\">self</span><span class=\"py\">.iter</span><span class=\"nf\">.as_mut</span><span class=\"p\">()</span><span class=\"nf\">.unwrap</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n            <span class=\"k\">if</span> <span class=\"cm\">/* unify with `id` and `tag` */</span> <span class=\"p\">{</span>\n                <span class=\"k\">return</span> <span class=\"k\">true</span><span class=\"p\">;</span>\n            <span class=\"p\">}</span>\n        <span class=\"p\">}</span>\n\n        <span class=\"k\">false</span>\n    <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>Note that because we allow arbitrary data to be used inside tags, including strings, numbers, and the like, we probably want a way to construct keys for these terms which represent their general structure, rather than concrete values. This is represented by the <code class=\"language-plaintext highlighter-rouge\">Tag</code> type in the snippet above, as opposed to <code class=\"language-plaintext highlighter-rouge\">TagData</code> which would store the actual concrete value of a tag.</p>\n\n<p>In cases where the tag is a compound term, we can simply use the functor as the index:</p>\n\n<div class=\"language-prolog highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"nv\">_</span><span class=\"p\">,</span> <span class=\"nv\">E</span><span class=\"p\">,</span> <span class=\"ss\">todo</span><span class=\"p\">(</span><span class=\"nv\">_</span><span class=\"p\">)).</span> <span class=\"c1\">% search by `todo/1`</span>\n</code></pre></div></div>\n\n<p>We could also choose to index based on a predetermined number of initial arguments, or allow users to choose their own indexing strategies based on the intended usage of the tags, etc.</p>\n\n<p>So far so good, but imagine you have a query even slightly more complex than a single tag predicate:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>@thought, @cite(_, _)\n</code></pre></div></div>\n\n<p>The predicate invocation would look like this:</p>\n\n<div class=\"language-prolog highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"nv\">C</span><span class=\"p\">,</span> <span class=\"nv\">E</span><span class=\"p\">,</span> <span class=\"ss\">thought</span><span class=\"p\">),</span> <span class=\"ss\">entry_tag</span><span class=\"p\">(</span><span class=\"nv\">C</span><span class=\"p\">,</span> <span class=\"nv\">E</span><span class=\"p\">,</span> <span class=\"ss\">cite</span><span class=\"p\">(</span><span class=\"nv\">_</span><span class=\"p\">,</span> <span class=\"nv\">_</span><span class=\"p\">)).</span>\n</code></pre></div></div>\n\n<p>Without any additional optimizations, we suddenly arrive at quadratic time complexity. For each solution yielded by the first invocation of <code class=\"language-plaintext highlighter-rouge\">entry_tag</code>, Prolog will invoke the second one and the implementation will in turn attempt to unify each of its solutions with the solution yielded from the first invocation.</p>\n\n<p>Here’s a visualization of this using our initial example journal:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>?- entry_tag(C, E, thought).\n   index search yields `E = 3` and `E = 4`\n     ?- entry_tag(C, 3, cite(_, _)).\n        index search yields `E = 3` and `E = 5`\n          ?- 3 = 3. true\n          ?- 3 = 5. false\n     ?- entry_tag(C, 4, cite(_, _)).\n        index search yields `E = 3` and `E = 5`\n          ?- 4 = 3. false\n          ?- 4 = 5. false\n</code></pre></div></div>\n\n<p>In this example 4 cases were tested only for 1 to result in a valid solution: <code class=\"language-plaintext highlighter-rouge\">E = 3</code></p>\n\n<p>An easy first optimization is to traverse entries instead of tags when possible. We can simply detect whether <code class=\"language-plaintext highlighter-rouge\">entry_tag</code> is called with an already unified ID argument, and then choose to iterate over other tags in the same entry instead of all entries matching the tag. This should be much better, because entries will realistically contain much fewer tags than there will be entries matching that tag.</p>\n\n<div class=\"language-rust highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">impl</span> <span class=\"n\">Nondet</span> <span class=\"k\">for</span> <span class=\"n\">EntryTag</span> <span class=\"p\">{</span>\n    <span class=\"k\">fn</span> <span class=\"nf\">call</span><span class=\"p\">(</span><span class=\"o\">&amp;</span><span class=\"k\">mut</span> <span class=\"k\">self</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">ctx</span><span class=\"p\">,</span> <span class=\"n\">id</span><span class=\"p\">,</span> <span class=\"n\">tag</span><span class=\"p\">]:</span> <span class=\"o\">&amp;</span><span class=\"p\">[</span><span class=\"n\">Term</span><span class=\"p\">])</span> <span class=\"k\">-&gt;</span> <span class=\"nb\">bool</span> <span class=\"p\">{</span>\n        <span class=\"c1\">// ...</span>\n\n        <span class=\"k\">if</span> <span class=\"k\">self</span><span class=\"py\">.iter</span><span class=\"nf\">.is_none</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n            <span class=\"k\">if</span> <span class=\"k\">let</span> <span class=\"nf\">Some</span><span class=\"p\">(</span><span class=\"n\">id</span><span class=\"p\">)</span> <span class=\"o\">=</span> <span class=\"n\">id</span><span class=\"py\">.get</span><span class=\"p\">::</span><span class=\"o\">&lt;</span><span class=\"n\">EntryId</span><span class=\"o\">&gt;</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n                <span class=\"c1\">// initialize iterator based on `id`</span>\n            <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n                <span class=\"c1\">// initialize iterator based on `tag`</span>\n            <span class=\"p\">}</span>\n        <span class=\"p\">}</span>\n\n        <span class=\"c1\">// ...</span>\n    <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>The search should then look something like this:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>?- entry_tag(C, E, thought).\n   index search yields `E = 3` and `E = 4`\n     ?- entry_tag(C, 3, cite(_, _)).\n        entry contains tags `thought` and `cite(\"Einstein\", 1905)`\n          ?- cite(_, _) = thought. false\n          ?- cite(_, _) = cite(\"Einstein\", 1905). true\n     ?- entry_tag(C, 4, cite(_, _))\n        entry contains tags `thought`\n          ?- cite(_, _) = thought. false\n</code></pre></div></div>\n\n<p>So the index search helps us narrow down the initial search space, but then we fall back to linear scans assuming realistically smaller data sets.</p>\n\n<p>This is actually the way the predicate is designed and implemented at the time of writing this article. It strikes a good balance between implementation simplicity (if you can call it that) and performance. It hasn’t been stress-tested against large datasets nor properly benchmarked and this is something I’m not planning to do anytime soon, because I don’t expect the app to be used heavily by anyone in the near future :) Most of what I talk about here are ahead-of-time optimizations, you could maybe argue premature. Still, they are not arbitrary, as I try to think what kind of workloads can be reasonably expected given the application’s design.</p>\n\n<h2 id=\"taking-it-further\">Taking it further</h2>\n\n<p>The solution is of course still suboptimal for certain imaginable use cases. Namely, for a conjunction query like in the example above, the two tags may only share a small subset of associated entries, in which case the first specified tag will determine the time it takes to find them, performing a lot of wasted iterations. Ideally we would want to optimize finding solutions for arbitrary boolean expressions.</p>\n\n<p>This is obviously the opening of a huge rabbit hole, one I will surely be tempted to explore in the near future.</p>",
            "summary": "Designing a Database for a Prolog-based PKM Application",
            "date_published": "2026-07-10T00:00:00+00:00",
            "date_modified": "2026-07-10T00:00:00+00:00",
            "author": {
              "name": "Michał Wawrzynowicz <michalwa2003@gmail.com>"},
            "tags": []}]
}