- While I actually didn't want to work on the project today, one thing led to another and I ended up working on it.
- I was watching this video on exceptions and realized I could use a simple
IO atype for my functions instead ofExceptT...because allIO atypes can potentially throw. Combined with a customExceptioninstance for myAppError, I could potentially get rid of a fewrunExceptTs in the code — so I went about doing it and the whole app got converted into a simpleIO a(i.e., theApp awrapper stopped being anExceptT ...and became anIO a). There were some places where I had to handle things correctly so that errors were managed properly (like when trying to insert a link, the inner function crashes if the SQL insert query fails — but the handler that uses the inner function shouldn't crash, because failing for one feed item shouldn't fail others). - What I like about this approach is that I can have any throwable
IO a, and I can simply run it throughfailWith <AppError>and it will produce anIO aor throw with the right kind ofAppError. i.e, it takes anIO athat could throw aSomeExceptionand converts it into anIO athat could throw anAppError. - The codebase became much cleaner and reasoning about the steps is now far simpler. I also discovered there was one place where I was returning an
IO afor no reason — the function could be very pure and returnasimply. - After this, I couldn't stop. I ended up writing a simple command system for the app so I can now compile the binary and run a few commands like:
> rss-digest add <url> # this adds an XML URL to the database, correctly showing an error if the URL is already added or it's an invalid URL.
> rss-digest refresh # this fetches all feed links from all the RSS feeds in the database, and then updates the feed_items table.
> rss-digest purge # nukes the whole thing
- I wanted to check the size of the binary and it was a whopping 64MB. Ignorant me was surprised as heck. I asked ChatGPT about it and it said it was because the binary was packing everything to be self-sufficient. One of the options it gave was to use dynamic linking instead of static linking — I had concerns about this because how would that work if one were to distribute the binary? The reduction was anyway not all that great. There were other options it suggested like "no profiling", turning off debug mode, using some "O2" mode of optimization etc... nothing really worked.
- One thing I missed in the ChatGPT list of recommendations was to use the
stripcommand on the binary. Instead, I went to Stack Overflow and Google... and one of the suggestions there wasstriptoo. So I did that and the binary came down to 41MB which is still humongous (for comparison, GitHub's CLI toolghv2.49 is ~48MB).stripremoves all the symbols from the binary. I did a test run of the binary after doing thestripand it was working OK. (update: if I compile the binary with dynamic linking, and then do astrip, the binary size is about 200kb) - I'm quite happy about the way the project has shaped up so far. Might do a recap of what I learned, patterns that seem to emerge etc at some point.
Update:
- I did some more work as I was bored and couldn't stop being obsessed with the project for a bit.
- I wrote a Makefile because I was frequently running
cabal build ...andcabal install. This made the process quicker. - I found a problem where, when I added more constructors to my
Commanddata type, the compiler didn't warn about missing pattern matches in themainfunction where I was handling the command. It turns out I had to enable some flags (specifically-Wincomplete-patterns). I ended up using-Walland fixing a bunch of lint warnings like unused declarations and imports and adding type annotations where I hadn't written them out explicitly. This had zero impact on the built binary size, though. - I added a couple of commands to remove a feed and list all existing feeds. The remove feed command made me realize that I need to set up a foreign key relationship between the feeds_table and feeds — so that when I remove a feed, I also remove all the posts that came from that feed URL. I've noted this for later implementation.