<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><atom:link href="https://monkey-projects.be" rel="self" type="application/rss+xml"></atom:link><title>Monkey Projects Clojure Blog</title><link>https://monkey-projects.be</link><description>A blog about software development in general and more specifically Clojure.</description><lastBuildDate>Wed, 19 Aug 2026 07:35:47 +0000</lastBuildDate><generator>clj-rss</generator><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2026-08-19-ai/</guid><link>https://monkey-projects.be/blog/posts/2026-08-19-ai/</link><title>How I do AI</title><description>You can see I've been busy the past months, because it's been a while since my last post.  I've been working hard on a new release of MonkeyCI, with mostly speed improvements but also a new underlying infrastructure.  This required a lot of refactoring and some rewriting.  This is not always very exciting, to say the least.The Good</description><pubDate>Wed, 19 Aug 2026 00:00:00 +0000</pubDate></item><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2025-12-24-new-beginnings/</guid><link>https://monkey-projects.be/blog/posts/2025-12-24-new-beginnings/</link><title>Old and New</title><description>2025 is nearing it's end and as every year, I'm looking back and looking forward.
What have I accomplished this year?  What do I hope to do next year?  What have I
learned?  And which mistakes have I made?  The last two are equivalent: after all,
making mistakes is the best way to learn.  Some things are not mistakes at all,
but with the benefit of hindsight we now know that it was perhaps a bad choice,
based on information that was incomplete (or maybe even wrong).What have I been working on the past year?  MonkeyCI of
course.  The intention was to go live in the beginning of 2025.  But international
politics got in the way.  Once reliable suppliers and partners suddenly turned
out not to be reliable after all.  Or they didn't share my values anymore.  This
meant I had to do make new choices, which often led to technical modifications.
And these were not always trivial.  For example, I decided to move away from
Oracle Cloud (OCI) to a purely European cloud provider.  I selected
Scaleway, to which I later added Hetzner.
This was not purely for political reasons, but also because they offered
solutions that OCI did not.  Or it turned out to be financially more attractive.</description><pubDate>Wed, 24 Dec 2025 00:00:00 +0000</pubDate></item><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2025-04-16-trenches-1/</guid><link>https://monkey-projects.be/blog/posts/2025-04-16-trenches-1/</link><title>Stories from the trenches</title><description>I've been a professional software developer for a quarter of a century now.  And
before that, I was writing code in my room instead of going to college.  And
before that I wrote simple applications in Turbo Pascal and C when I was in
high school.  So you might say I've got some coding experience.  Of course, in
those beginner days, there was no internet.  So it wasn't easy to learn from
other people.  Nowadays, the possibilities have much expanded.  But still, from
time to time, I struggle with certain problems.  Ever since I've started writing
functional code (in my case, with Clojure) I have learned
even more about coding.  Since about a year and a half, I've been working on
MonkeyCI, a full-blown SaaS, completely written in
Clojure.And still I'm learning.  I have written several Clojure-based applications in
my previous company, but I've always had trouble with how state is managed.  This
is certainly a common problem in functional programming.  Actually, it's a common
problem in programming in general, but functional applications take extra care
to handle state well.  This means, we want to have to do as little as possible
with state.  Ideally, functional code is completely pure, with no side effects
whatsoever.  Which is of course unachievable, since applications without side
effects are useless.  But still we try to isolate the "state-part" of the process
as much as we can.  Whole libraries have been written on how to manage state and
side effects, but they remain difficult to handle.</description><pubDate>Wed, 16 Apr 2025 00:00:00 +0000</pubDate></item><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2024-04-03-manifold/</guid><link>https://monkey-projects.be/blog/posts/2024-04-03-manifold/</link><title>Manifold is your friend</title><description>I find myself writing async code fairly often.  Whenever I'm calling an external
service, usually using HTTP, I tend to use the async versions of the libraries
that provide it, like Aleph or HttpKit.
Actually, I like Aleph the most because it integrates nicely with Manifold.  That's not a coincidence, they're both written by the same authors.  The
usual go-to solution in the Clojure world when it comes to async programming is
of course core.async.  I've used it
several times, and since it's so often used, I try to use it whenever the "async
need" arises.  However, time and time again I find myself returning to Manifold.This probably is because Manifold is a bit more Clojure-esque.  The API is
more idiomatic, they're just functions.  Not those weird &lt;! and &gt;&gt;! things
core.async provides.  Also, I just find it easier to work with.  It allows you
to use deref where you want, which is a well-known function and also well
supported by other libraries.  Another big advantage IMHO is that Manifold
differntiates between a stream and a "deferred".  The latter is a single value
that may or may not be "realized" in the future.  Usually the functions the
lib provides are a mix of both.  For example, when fetching a value from a stream
using take! it
returns another deferred.  And very often a stream is not what you want, but
a deferred value fits nicely.  In core.async they solve this by returning
channels (as it's called there) that only hold one value.  Of course, you could
also do this using Manifold but you don't have to.</description><pubDate>Wed, 03 Apr 2024 00:00:00 +0000</pubDate></item><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2024-01-12-multimethods/</guid><link>https://monkey-projects.be/blog/posts/2024-01-12-multimethods/</link><title>The power of multimethods</title><description>In a previous post about "real" functional
development, I hinted at the additional tools that Clojure provides to handle actual, non-trivial
programming situations.  Pushing out the side effects by creating a higher-order function
is nice, but it may become tedious to have to pass it along all the time, throughout multiple
call levels.  You also don't always have control over the entire call stack.  What if you're
invoking a library that deep down calls another one that performs some side effect?  For
situations like these you can use multimethods.
This is actually something that was already present in Lisp and is a kind of syntactic sugar
that behaves like the combination of higher-order functions and state.  It's a function without
an implementation, but with a "decider" function that receives the same arguments, and whose
return value is used to select the correct implementation.  An example may make it clearer:</description><pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate></item><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2023-12-21-real-functional-development/</guid><link>https://monkey-projects.be/blog/posts/2023-12-21-real-functional-development/</link><title>Real Functional Development</title><description>Functional programming is great, we all know that, don't we?  But we also want
to build applications for the real world.  That means they actually have to do
something.  Those textbooks about functional development mostly focus on theoretical
things and come up with convoluted examples about bank accounts that somehow don't
need any storage or anything.  Perhaps the writers see that as some kind of
implementation detail?  But of course it's not.  Actually, as it happens so often,
the devil is again in the details.  Because how do you write software that is
both functional and has side effects?  Writing to files, reading from network
connections, talking to database servers, etc.  All those things are what makes
your application tick, but they are also not purely functional.  It's not possible
to call functions doing something like that and just inspecting the return values,
and then calling them again as if the previous call never happened.This also makes TDD more complicated.  The problem is the same with imperative
programming, of course.  There they solve it using interfaces, facades and other
OOP design patterns.  Functional
programming doesn't really have design patterns, because they are (arguably) not needed.
Since functions are first-class citizens, you can just create a higher-order function and
pass in the "implementation details" as an argument.  For example:</description><pubDate>Thu, 21 Dec 2023 00:00:00 +0000</pubDate></item><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2023-11-07-emerging-design/</guid><link>https://monkey-projects.be/blog/posts/2023-11-07-emerging-design/</link><title>Emerging Design</title><description>I'm a big fan of emerging design.  Instead of doing a "big design up front"
(or BDUF in short), we design the application as we go along.  So actually,
no design?  No, not really.  There is some global vision as to where we want
to go, of course.  But we don't go into the details all that much.  Why?
Because I think that the BDUF always ends up with a wrong design.  Sometimes
due to changing requirements, but more because it's just impossible to know
everything beforehand, even if you think about it really hard (and long).So how does this work in practice?  I generally set some priorities, and
make some obvious decisions, like what platform, or should it be a web app?
But even these decisions are not carved in stone.  I try to keep my options
open as much as possible, and I usually take the path of least resistance.
TDD helps a lot here: because I'm constantly writing tests and then the
minimal amount of code to make them pass, I tend to end up with very basic
implementations.  Like using files instead of a relational database.  Or
some local process instead of a network call.  This can all be changed
later.  That's why the design is emerging: every once in a while I take
a step back and look at the overall architecture.  And since we're constantly
becoming smarter, that architecture will most likely be better than anything
I would have thought about up front.</description><pubDate>Tue, 07 Nov 2023 00:00:00 +0000</pubDate></item><item><guid isPermaLink="false">https://monkey-projects.be/blog/posts/2023-09-08-first-page/</guid><link>https://monkey-projects.be/blog/posts/2023-09-08-first-page/</link><title>Introducing Monkey Projects Blog</title><description>Hi and welcome to the first post of the Monkey Projects development blog!  Glad
to see you here, and expect much more to come.  After almost a quarter century
of building software for others, I've decided to move on to a new project and I
want to share that experience with the world.  And since I've built up a lot of
experience in the past, I think this future experience will benefit from.  And
so will you, I think!So what can you expect here?  I'll be writing about:</description><pubDate>Fri, 08 Sep 2023 00:00:00 +0000</pubDate></item></channel></rss>