<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <generator>Persumi - Level up your writing and blogging with AI</generator>
  <category label="Blog" scheme="http://persumi.com/u/fredwu/tech/e/blog" term="blog"/>
  <category label="Tech" scheme="http://persumi.com/u/fredwu/tech" term="tech"/>
  <link href="http://persumi.com/u/fredwu/tech/e/blog/t/elixir"/>
  <link href="http://persumi.com/u/fredwu/tech/e/blog/t/elixir/feed/rss"/>
  <link rel="self" href="http://persumi.com/u/fredwu/tech/e/blog/t/elixir/feed/atom"/>
  <author>
    <name>Fred Wu</name>
    <email>ifredwu@gmail.com</email>
    <uri>http://persumi.com/u/fredwu</uri>
  </author>
  <subtitle/>
  <id>http://persumi.com/u/fredwu/tech/e/blog/t/elixir</id>
  <title>Blog (elixir) - Fred Wu&apos;s Tech</title>
  <updated>2026-04-17T12:41:05.663632Z</updated>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
A few weeks ago I &lt;a href=&quot;welcome-to-persumi-a-modern-platform-for-content-creation&quot;&gt;soft launched&lt;/a&gt; an MVP - you are looking at it right now.&lt;/p&gt;
&lt;p&gt;
In this post I’ll talk about the features, the tech stack and the globally distributed infrastructure behind building this MVP, and of course, with a sprinkle of learnings too.&lt;/p&gt;
&lt;h2&gt;
The “MVP”&lt;/h2&gt;
&lt;p&gt;
Deciding what makes up an “MVP” is always interesting - I’ve heard some saying if the product &lt;em&gt;isn’t&lt;/em&gt; embarrassing you’re releasing it too late, and also if the product &lt;em&gt;is&lt;/em&gt; embarrassing, you’re not gonna make it.&lt;/p&gt;
&lt;p&gt;
For me, I’ve always had the idea of building all the essential features as part of the MVP, with one or two “hero” features that would differentiate the product from the competitions, and then build out more premium features over time.&lt;/p&gt;
&lt;p&gt;
If you can’t already tell, Persumi is a content creation platform with some social networking features. It may sound bland, but what I believe makes it stand out, is the desire of putting the focus back onto the content, rather than the VC-fuelled, ever increasing appetite for more ads and user hostile features.&lt;/p&gt;
&lt;h2&gt;
The Long Nights and Weekends&lt;/h2&gt;
&lt;p&gt;
There is no magic beans for productivity - especially when I have a full time job. Working on a side hustle means giving up on almost all social and entertainment activities. It’s not for everyone, but I didn’t mind it too much. Being an introvert definitely helped - I was happy to see night by night the MVP gradually taking shape to become more and more real.&lt;/p&gt;
&lt;p&gt;
Looking back, I spent about three months to build out most of the MVP, then another week or two on infrastructure, and another week or two for polishing, all whilst having a full time job.&lt;/p&gt;
&lt;p&gt;
It’s been a journey, I’m glad that it “only” took me 3-4 months to get to this stage, as initially I estimated for a 6+ months MVP build.&lt;/p&gt;
&lt;h2&gt;
The Features&lt;/h2&gt;
&lt;p&gt;
With all that in mind, I’ve set out to build the essential features that make a blogging and social networking platform:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Short form content like a tweet  &lt;/li&gt;
  &lt;li&gt;
Long form content like a blog post or a book chapter  &lt;/li&gt;
  &lt;li&gt;
RSS feeds  &lt;/li&gt;
  &lt;li&gt;
Communities similar to forums and sub-reddits  &lt;/li&gt;
  &lt;li&gt;
Direct messaging between users  &lt;/li&gt;
  &lt;li&gt;
A voting (like/dislike) system  &lt;/li&gt;
  &lt;li&gt;
A bunch of CRUD glue pieces to make all these things work  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
The “Hero” Features&lt;/h3&gt;
&lt;p&gt;
Beyond these seemly unremarkable features, I’ve also had in mind two key features that would differentiate the platform from the rest:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The “persona” concept, whereby each user is allowed to have multiple personas to hold different content or topics of interest, e.g. a persona for professional stuff, a persona for gaming stuff and a persona for travel stuff, etc  &lt;/li&gt;
  &lt;li&gt;
AI generated audio content for text (also known as Text-to-Speech)  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
These two “hero” features are what drove me to build Persumi in the first place. Together, they solve some very real pain points for me, namely:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Following specific topics of interest from people is difficult, with the algorithms taking over people’s home feeds, there are simply way too much noise, thanks to VC-fuelled “user engagement” metrics  &lt;/li&gt;
  &lt;li&gt;
Content consumption on the go (e.g. during commute or during workouts, etc) is becoming more and more prevalent, but the traditional platforms haven’t adopted to this new lifestyle other than shoving short form content down our throats  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
There’s also a third “hero” feature: the Aura system. Unlike the upvote/downvote or like/dislike buttons in many social platforms that only serve the algorithm to push more content to you, Persumi’s Aura system keeps track of user’s content quality over time, and would punish the low quality content and promote high quality content using visual cues - lower quality content has a much lower contrast making them easy to ignore. In the age of social media, self-curating content becomes essential to keep a platform healthy, engaging and usable.&lt;/p&gt;
&lt;h3&gt;
The Non-MVP Features, a.k.a. The Future&lt;/h3&gt;
&lt;p&gt;
There are many features that didn’t make the MVP cut, most of these are value-added features that will eventually make their way into paid subscriptions - if Persumi gains enough traction to attract users who don’t mind paying for premium features.&lt;/p&gt;
&lt;p&gt;
A prime example of such paid features is ones that help users monetise their content, e.g. ad revenue sharing and paid subscribers (like Patreon).&lt;/p&gt;
&lt;p&gt;
I also have the ambition of building out Persumi’s features so it can eventually compete against the likes of LinkedIn and Tinder.&lt;/p&gt;
&lt;p&gt;
Wouldn’t it be better for the world to have a platform like Persumi that doesn’t focus on dark patterns and exploiting users? 😉&lt;/p&gt;
&lt;h2&gt;
The Tech Stack&lt;/h2&gt;
&lt;p&gt;
Over the past decade or so I’ve mainly worked with two tech stacks: Ruby and Elixir. So naturally, Persumi was going to be built using one of them.&lt;/p&gt;
&lt;p&gt;
After some consideration, I’ve decided to go ahead with Elixir, the main reasons were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Elixir and Erlang/OTP support distributed systems out of box  &lt;/li&gt;
  &lt;li&gt;
I’ve been writing more Elixir than Ruby lately, so I’m more productive in Elixir  &lt;/li&gt;
  &lt;li&gt;
I really wanted to try and use &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_view&quot;&gt;LiveView&lt;/a&gt; in production  &lt;/li&gt;
  &lt;li&gt;
I prefer Phoenix’s application architecture more than Rails’  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
On top of Elixir, I’ve decided early on a few other things to go with it:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Tailwind for CSS  &lt;/li&gt;
  &lt;li&gt;
Postgres for database, preferably a serverless option  &lt;/li&gt;
  &lt;li&gt;
A search engine  &lt;/li&gt;
  &lt;li&gt;
An easy to maintain infrastructure that doesn’t cost an arm and a leg  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
Elixir&lt;/h3&gt;
&lt;p&gt;
I first discovered Elixir in 2014 while I was still actively involved in the Ruby and Rails communities, but it was two years later that I had the opportunity to really dive into it. I built a few open source libraries to help me learn Elixir and OTP:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/crawler&quot;&gt;Crawler&lt;/a&gt; - a high performance web scraper.  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/opq&quot;&gt;OPQ: One Pooled Queue&lt;/a&gt; - a simple, in-memory FIFO queue with back-pressure support, built for Crawler.  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt; - a Naive Bayes machine learning implementation. Hey, I was doing machine learning before it was mainstream! 😆  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt; - an English (Porter2) stemming implementation, built for Simple Bayes.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
With a decent amount of experience building Phoenix web apps over the years, I unfortunately never had the opportunity to use LiveView…&lt;/p&gt;
&lt;p&gt;
I guess that’s finally changed now.&lt;/p&gt;
&lt;p&gt;
LiveView has been amazing, not only does it drastically reduce the amount of front-end code you have to write, make the entire web app feel super snappy, but it also virtually eliminates any front-end and back-end code/logic duplications. It is such an awesome piece of technology that improves both the user experience and the developer experience.&lt;/p&gt;
&lt;h4&gt;
Petal Pro&lt;/h4&gt;
&lt;p&gt;
During my initial tech research, I came across &lt;a href=&quot;https://petal.build/&quot;&gt;Petal Pro&lt;/a&gt; which is a boilerplate starter template built on top of Phoenix. It handles things like user authentication which almost every web app needs, but is somewhat tedious to build.&lt;/p&gt;
&lt;p&gt;
Petal Pro isn’t free but it ended up saving me so much time. I also started contributing small bug fixes and features to it too. If you are about to build something in Phoenix, check it out!&lt;/p&gt;
&lt;h3&gt;
Tailwind CSS&lt;/h3&gt;
&lt;p&gt;
As I kept progressing my career, there have been fewer and fewer opportunities for me to write front-end and CSS code. Last time I rebuilt my blog, I used &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt; - that was 2019. Since then Tailwind has gained a lot more traction, so I wanted an excuse to try finally give it a shot.&lt;/p&gt;
&lt;p&gt;
There is a debate on how many new things you should try for building your MVP - the more you have to learn, the slower your MVP progresses. That said, given CSS is reasonably straightforward, I figured it wouldn’t slow me down too much, if anything, Tailwind’s flexibility might just eventually make up any time lost in learning.&lt;/p&gt;
&lt;p&gt;
I’m happy to report that it is indeed true - by using Tailwind, it became significantly easier for me to customise my components and elements. I can see why it became so popular. It’s not for everyone, but I like it.&lt;/p&gt;
&lt;h3&gt;
Postgres&lt;/h3&gt;
&lt;p&gt;
Choosing Postgres as a database was a no brainer, given how popular and versatile it is. I did briefly consider NoSQL options like DynamoDB but quickly wrote them off as I needed an RDBMS to get things off the ground quickly, and the DB is unlikely to be the bottleneck for a long time anyway.&lt;/p&gt;
&lt;p&gt;
In Elixir, the &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.html&quot;&gt;Ecto library&lt;/a&gt; works wonders for Postgres.&lt;/p&gt;
&lt;p&gt;
Later in the post I’ll touch on how I deploy and run Postgres in production.&lt;/p&gt;
&lt;h3&gt;
Search Engine&lt;/h3&gt;
&lt;p&gt;
For a search engine, my requirements were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The ability to search across multiple fields of a schema  &lt;/li&gt;
  &lt;li&gt;
The ability to rank them  &lt;/li&gt;
  &lt;li&gt;
The ability to have typo tolerance, word stemming and other similar language features to make search more intuitive  &lt;/li&gt;
  &lt;li&gt;
The ability to search multiple languages, including CJK (Chinese/Japanese/Korean) characters  &lt;/li&gt;
  &lt;li&gt;
Simple to run  &lt;/li&gt;
  &lt;li&gt;
Cheap to run  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Using Postgres’ full text search was probably going to be the simplest but it doesn’t offer all the functionalities I need without a bunch of set up and manual SQL queries so I didn’t pursue it.&lt;/p&gt;
&lt;p&gt;
Elasticsearch on the other hand, offers good search functionalities but takes a bit of effort to set up and maintain, and can be costly to run.&lt;/p&gt;
&lt;p&gt;
After doing some more research, I found the following three options that would fit my needs:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.algolia.com/&quot;&gt;Algolia&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.meilisearch.com/&quot;&gt;Meilisearch&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://typesense.org/&quot;&gt;Typesense&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Both Meilisearch and Typesense are open source, with commercial SaaS offerings, whilst Algolia is SaaS-only.&lt;/p&gt;
&lt;p&gt;
It’s been an interesting journey. I started with Typesense as I liked what I read, but I quickly discovered that it doesn’t search Chinese characters properly.&lt;/p&gt;
&lt;p&gt;
I then turned to Meilisearch. I especially liked the fact that they offered a generous free tier SaaS to get you off the ground running. Spoiler: during my implementation they did a bait and switch and removed the free tier.&lt;/p&gt;
&lt;p&gt;
At the time the Elixir support for Meilisearch wasn’t up to date, so I ended up &lt;a href=&quot;https://github.com/nutshell-lab/meilisearch-ex/pull/5&quot;&gt;contributing to a community library&lt;/a&gt; to add the features I needed.&lt;/p&gt;
&lt;p&gt;
Curious timing, after Meilisearch removed their free tier, I discovered that even though they officially support searching for Chinese characters, the implementation wasn’t perfect. I found some edge cases where characters weren’t detected properly, making the search results unreliable.&lt;/p&gt;
&lt;p&gt;
So, my last hope was Algolia. Despite them being the more expensive option out of the three, it does offer a free tier. It turns out, their search results for Chinese characters were much better than Meilisearch’s. Luckily, re-implementing the search from Meilisearch to Algolia didn’t take too much effort, it was pretty much done in one night.&lt;/p&gt;
&lt;h3&gt;
Infrastructure&lt;/h3&gt;
&lt;p&gt;
Early on during the development I’d already determined I wanted to try &lt;a href=&quot;https://fly.io/&quot;&gt;Fly&lt;/a&gt; and &lt;a href=&quot;https://neon.tech/&quot;&gt;Neon&lt;/a&gt;, for web and DB, respectively.&lt;/p&gt;
&lt;p&gt;
I am in no way associated with either company, I was curious about Fly due to its tie-in with the Elixir community (Phoenix Framework’s author Chris McCord works there), and Neon due to its serverless nature.&lt;/p&gt;
&lt;h4&gt;
Globally Distributed Infra&lt;/h4&gt;
&lt;p&gt;
With Fly, the infrastructure automatically becomes globally distributed as soon as I started provisioning servers in more than one region. As of the time of writing, Persumi is deployed to US West, Australia and EU.&lt;/p&gt;
&lt;p&gt;
Despite being simple to use, making Fly work initially actually took quite a bit of finessing due to its incomplete official documentation and flakiness. Some of the services were having issues during the course of my MVP development. Worse, they don’t report (or sometimes even acknowledge) the issues unless they are region-wide outages. To this date, I believe their blue/green deployment strategy which was recently introduced, is still buggy, I often have to use their rolling deployment strategy instead. Deployment logs were provided to Fly but I think they’re too busy with other things…&lt;/p&gt;
&lt;p&gt;
Still, I’m sticking with them for now due to the ease of use after the initial hurdle, and their globally distributed infrastructure without asking for my kidney.&lt;/p&gt;
&lt;p&gt;
To augment Fly’s web servers, I also use Cloudflare’s &lt;a href=&quot;https://www.cloudflare.com/application-services/products/cdn/&quot;&gt;CDN&lt;/a&gt; as well as &lt;a href=&quot;https://www.cloudflare.com/developer-platform/r2/&quot;&gt;R2&lt;/a&gt; to serve asset files and audio files.&lt;/p&gt;
&lt;p&gt;
Funny tangent, initially I used &lt;a href=&quot;https://bunny.net/&quot;&gt;Bunny&lt;/a&gt; for asset files and CDN, as I misread Cloudflare’s terms and thought I couldn’t serve audio files from Cloudflare. Bunny worked okay but their dashboard for some reason was painfully slow - not a good look for a CDN company. Like the search engine switch, it didn’t take me too long to switch over to Cloudflare.&lt;/p&gt;
&lt;h4&gt;
Serverless Postgres&lt;/h4&gt;
&lt;p&gt;
There are a few options to run Postgres:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
Run on a standard server for maximum portability, but it requires more server maintenance overhead  &lt;/li&gt;
  &lt;li&gt;
Run on AWS RDS/Aurora or a similar managed service, easy but can be costly  &lt;/li&gt;
  &lt;li&gt;
Run on a serverless option such as Aurora Serverless or Neon  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
For my use case, I think option 2 or 3 are better fitting. As I mentioned earlier, I started the experiment with &lt;a href=&quot;https://neon.tech/&quot;&gt;Neon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Neon worked well initially, until I started deploying Fly instances in multiple regions. Due to Neon being only available in one region (I chose US West), and I live in Australia, the round trips between Fly’s Australian instance and Neon’s US instance were a show stopper - especially when complex DB transactions were involved. Actions sometimes took &lt;em&gt;seconds&lt;/em&gt; to complete, yikes.&lt;/p&gt;
&lt;p&gt;
Despite Fly not offering a managed Postgres service, I ended up trying it anyway due to its &lt;a href=&quot;https://fly.io/docs/postgres/advanced-guides/high-availability-and-global-replication/&quot;&gt;distributed nature&lt;/a&gt;. After incorporating &lt;a href=&quot;https://github.com/superfly/fly_postgres_elixir&quot;&gt;Fly Postgres&lt;/a&gt; in the app, all DB operations immediately became more responsive. Paired with LiveView, it feels like running the application locally.&lt;/p&gt;
&lt;p&gt;
The current Persumi infra looks like:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
1 x Fly instance in US West, always on  &lt;/li&gt;
  &lt;li&gt;
1 x Fly instance in Australia, auto-shutdown when there’s no traffic  &lt;/li&gt;
  &lt;li&gt;
1 x Fly instance in Netherlands, auto-shutdown when there’s no traffic  &lt;/li&gt;
  &lt;li&gt;
1 x Fly Postgres writer instance in US West, always on  &lt;/li&gt;
  &lt;li&gt;
1 x Fly Postgres read replica instance in Australia, always on  &lt;/li&gt;
  &lt;li&gt;
1 x Fly Postgres read replica instance in Netherlands, always on  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
With this setup, I think I’m quite happy with the cost and scalability balance - it costs ~$20/m to run, with the potential of both vertical and horizontal scaling with ease.&lt;/p&gt;
&lt;h2&gt;
The Missteps&lt;/h2&gt;
&lt;p&gt;
The search engine and CDN swaps mentioned earlier certainly took away some of my time, but they were nothing compared to a major misstep I encountered.&lt;/p&gt;
&lt;p&gt;
And that was:  the choice of how machine learning is done.&lt;/p&gt;
&lt;p&gt;
Let me explain.&lt;/p&gt;
&lt;h3&gt;
Machine Learning, and Inference&lt;/h3&gt;
&lt;p&gt;
Even before I started the first line of code, I already painted a picture in my head on the machine learning needed: a TTS (text-to-speech) model that I could run inference locally on the instance.&lt;/p&gt;
&lt;p&gt;
The reason being I believed it was the more flexible approach to gradually improve the inference  and therefore the end result by training my own AI models over time.&lt;/p&gt;
&lt;p&gt;
Given I didn’t want to rent expensive GPU instances, I opted for fast TTS models that could do near real-time inference on CPUs. I used &lt;a href=&quot;https://github.com/coqui-ai/TTS&quot;&gt;Coqui TTS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
The resulting out-of-box audio wasn’t great, but I kept pressing on.&lt;/p&gt;
&lt;p&gt;
The show stopper came when it was time to deploy everything onto Fly. Due to Fly’s architecture (they deploy small-ish Docker images, &lt; 2GB each, onto their global network), I struggled to keep the Docker image file small enough to be able to deploy. With Coqui TTS, I would need Python and all the dependencies that resulted in a Docker image around 4-5GB in size.&lt;/p&gt;
&lt;p&gt;
With my tunnel vision, I then chose to offload the entire Python and Coqui TTS dependency tree onto Fly’s &lt;a href=&quot;https://fly.io/docs/reference/volumes/&quot;&gt;persistent volumes&lt;/a&gt;. I knew it wasn’t a great option, as that meant my infrastructure (other than the database) was no longer immutable.&lt;/p&gt;
&lt;p&gt;
Sometimes it’s necessary to take a step back, re-evaluate, and then press on in a different direction. Which thankfully I did.&lt;/p&gt;
&lt;p&gt;
The new direction is quite simple really: instead of performing inference locally, use an external service instead.&lt;/p&gt;
&lt;p&gt;
After doing a quick comparison between the offerings from AWS, Azure and GCP, I ended up using &lt;a href=&quot;https://cloud.google.com/text-to-speech&quot;&gt;Google’s TTS&lt;/a&gt;. Honestly I think I would’ve been happy with any of the options, they all seem to have decent neural based TTS.&lt;/p&gt;
&lt;p&gt;
In hindsight, these giant corporations have much more resources and expertise to train better models than I ever could on my own.&lt;/p&gt;
&lt;p&gt;
The end result:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The TTS sounds significantly better than before  &lt;/li&gt;
  &lt;li&gt;
It’s just as cheap to run (Google offers a certain amount of free TTS API calls per month)  &lt;/li&gt;
  &lt;li&gt;
It no longer needs complex Python calls and FFmpeg calls to make local TTS work  &lt;/li&gt;
  &lt;li&gt;
The Fly infrastructure is simple and immutable again  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
In hindsight, I never should’ve even entertained the idea of running ML locally on CPUs, no matter how simple and efficient a model might be.&lt;/p&gt;
&lt;p&gt;
That said, with TTS, it wasn’t as simple as just calling the APIs and getting the perfect resulting audio back. Some pre and post processing were needed, but that’s a topic for another time.&lt;/p&gt;
&lt;h3&gt;
More Machine Learning&lt;/h3&gt;
&lt;p&gt;
The cherry on top - now that Google’s APIs were integrated into the app, I ended up also using Google’s &lt;a href=&quot;https://ai.google/discover/palm2/&quot;&gt;PaLM 2&lt;/a&gt; to do text summarisation (it was initially done locally too) as well as for a ChatGPT-like AI prompt service, to power Persumi’s AI writing assistance feature.&lt;/p&gt;
&lt;h2&gt;
The Closing&lt;/h2&gt;
&lt;p&gt;
If you read this far, thank you! I hope you enjoyed reading (or listening) to this post. Please look around and kick tyres, I would love your feedback on how to improve Persumi.&lt;/p&gt;
&lt;p&gt;
Sign up for an account if you haven’t already, and leave a comment if you have any questions. Until next time!&lt;/p&gt;
]]&gt;</content>
    <published>2023-08-09T09:28:12.135201Z</published>
    <category label="Blog" scheme="http://persumi.com/u/fredwu/tech/e/blog" term="blog"/>
    <category label="Tech" scheme="http://persumi.com/u/fredwu/tech" term="tech"/>
    <link href="http://persumi.com/c/persumi/u/fredwu/p/how-i-built-a-mostly-feature-complete-mvp-in-3-months-whilst-working-full-time"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/c/persumi/u/fredwu/p/how-i-built-a-mostly-feature-complete-mvp-in-3-months-whilst-working-full-time</id>
    <title>How I Built a Mostly Feature-Complete MVP in 3 Months Whilst Working Full-Time</title>
    <updated>2023-08-09T09:28:12.135201Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I’ve had a productive coding weekend, and so I decided to share my experience. Now, many developers choose to treat their career as a series of 9-5 jobs, but if you’re reading this, I assume you’re like the rest of us who love continuous learning and self improvement.&lt;/p&gt;
&lt;h2&gt;
Preface&lt;/h2&gt;
&lt;p&gt;
About a year ago I started learning Elixir. So as part of the learning experience, I wrote two matching learning related libraries: &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt; and &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;. It was a great, really enjoyable experience and I learnt a lot about the concepts of &lt;a href=&quot;https://en.wikipedia.org/wiki/Stemming&quot;&gt;word stemming&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Naive_Bayes_classifier&quot;&gt;naive Bayes classification&lt;/a&gt; and of course, functional programming.&lt;/p&gt;
&lt;p&gt;
These topics have been interesting to learn about, but until one gets to use them on a daily basis for a while, key concepts are less likely to convert from short term memory to long term memory. Given my day job is not about writing Elixir (yet), I needed to find other ways to keep my skill-level up and to continue exploring new things.&lt;/p&gt;
&lt;p&gt;
So about a month ago, I picked up a project I started a year ago but gave up shortly after: &lt;a href=&quot;https://github.com/fredwu/crawler&quot;&gt;Crawler&lt;/a&gt;. At the time &lt;a href=&quot;https://elixir-lang.org/blog/2016/07/14/announcing-genstage/&quot;&gt;GenStage was just announced&lt;/a&gt; and I was interested in incorporating it into my project as I thought it’d be a great fit. But due to varies reasons - mostly not having a firm grasp of the GenStage concept and implementation, as well as taking on a CTO role at a startup, I couldn’t find enough time and patience to make it work so I had to let it go.&lt;/p&gt;
&lt;p&gt;
Until now.&lt;/p&gt;
&lt;h2&gt;
Crawler, on Steroids&lt;/h2&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/crawler-architecture.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
I’d realised that the way I was trying to incorporate GenStage into my project was never going to work. Not because of GenStage itself, but because of &lt;strong&gt;the way I approached learning it&lt;/strong&gt;. At the time I was so eager to make use of GenStage, and coming off the back of my good streak of releasing the aforementioned machine learning libraries, I thought I could take shortcuts and things would all work out perfectly.&lt;/p&gt;
&lt;p&gt;
No.&lt;/p&gt;
&lt;p&gt;
So I licked the wounds, learnt my mistakes and changed tactics. This time, I scoped out and encapsulated my learnings (just as one would in designing a software system), and eventually I came up with another library - &lt;a href=&quot;https://github.com/fredwu/opq&quot;&gt;OPQ: One Pooled Queue&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
OPQ: One Pooled Queue&lt;/h2&gt;
&lt;p&gt;
I knew Crawler could technically work without any queueing system, or GenStage. So I kept on building Crawler, until I felt productive in writing Elixir again, and touched enough areas and concepts that I knew exactly what I needed from GenStage.&lt;/p&gt;
&lt;p&gt;
And luckily for me too, GenStage over the past year has matured and more importantly, has better documentation with more code examples. Upon closer investigation of the code examples, I found that their &lt;a href=&quot;https://github.com/elixir-lang/gen_stage/blob/v0.12.2/examples/gen_event.exs&quot;&gt;GenEvent&lt;/a&gt; and &lt;a href=&quot;https://github.com/elixir-lang/gen_stage/blob/v0.12.2/examples/rate_limiter.exs&quot;&gt;RateLimiter&lt;/a&gt; examples were almost exactly what I needed. It was an epiphany moment for me after reading and understanding these examples, all of a sudden I “get it”.&lt;/p&gt;
&lt;p&gt;
If you take a look at the source code of &lt;a href=&quot;https://github.com/fredwu/opq&quot;&gt;OPQ&lt;/a&gt; you’ll notice that the heavy lifting logic was mostly inspired (or even copy-pasted) from those examples.&lt;/p&gt;
&lt;h2&gt;
Open Sourcing is Contributing and Caring&lt;/h2&gt;
&lt;p&gt;
Up until this point, as far as writing open source Elixir code goes, it had mostly been me writing my own code for my own projects. But open sourcing is much more than just writing one’s own code and publishing them on Github.&lt;/p&gt;
&lt;p&gt;
If you’ve followed my work you’ll know that I’m a big fan of contributing to other projects, some of which are well-known ones like &lt;a href=&quot;http://contributors.rubyonrails.org/contributors/fred-wu/commits&quot;&gt;Rails&lt;/a&gt; and &lt;a href=&quot;https://github.com/slim-template&quot;&gt;Slim&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
It just so happens, that over the weekend I ran into situations where I needed to contribute to other Elixir projects - and spoiler alert: one of which is Elixir itself.&lt;/p&gt;
&lt;h2&gt;
ElixirRetry&lt;/h2&gt;
&lt;p&gt;
One of the features I set out to add to Crawler, was to allow failed crawls to retry before giving up. Naturally, the first thing I did was to try find an existing package to support the retry functionality.&lt;/p&gt;
&lt;p&gt;
After some googling and digging into some source code, I’ve found and settled on &lt;a href=&quot;https://github.com/safwank/ElixirRetry&quot;&gt;ElixirRetry&lt;/a&gt; - a neat library that was cleverly built.&lt;/p&gt;
&lt;p&gt;
Soon I found out that since its last release, it offered both &lt;code class=&quot;inline&quot;&gt;retry/2&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;retry/3&lt;/code&gt;, the latter of which was to support an extra argument that specifies which exceptions to allow as part of the retry flow. It was a great addition, but it doesn’t effect me as Crawler doesn’t need it.&lt;/p&gt;
&lt;p&gt;
As a developer who cares deeply about code quality and clarity, I immediately thought about how the interfaces for &lt;code class=&quot;inline&quot;&gt;retry/2&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;retry/3&lt;/code&gt; could be improved, by simply combining them and making the extra argument in &lt;code class=&quot;inline&quot;&gt;retry/3&lt;/code&gt; an option.&lt;/p&gt;
&lt;p&gt;
So I did, and issued a pull request here: &lt;a href=&quot;https://github.com/safwank/ElixirRetry/pull/12&quot;&gt;https://github.com/safwank/ElixirRetry/pull/12&lt;/a&gt; You’ll notice that I’ve also made some improvements to the test suite while I was at it. ;)&lt;/p&gt;
&lt;h2&gt;
Elixir Typespec&lt;/h2&gt;
&lt;p&gt;
One issue I ran into while building Crawler, was the static analysis via &lt;a href=&quot;http://erlang.org/doc/man/dialyzer.html&quot;&gt;Dialyzer&lt;/a&gt; - some code that actually ran and passed tests functionally, failed the type checking.&lt;/p&gt;
&lt;p&gt;
As someone who isn’t as experienced in Elixir, I first &lt;a href=&quot;https://github.com/elixir-lang/elixir/issues/6507&quot;&gt;opened an issue&lt;/a&gt; and phrased it more as a question seeking validity of the issue. I then jumped on &lt;a href=&quot;https://elixir-slackin.herokuapp.com/&quot;&gt;Elixir’s Slack group&lt;/a&gt; and asked people in the group about this issue.&lt;/p&gt;
&lt;p&gt;
Fortunately, &lt;a href=&quot;https://github.com/benwilson512&quot;&gt;Ben Wilson&lt;/a&gt; immediately came to aid by verifying my issue and validating my suspicion of it being an issue in Elixir’s typespec documentation.&lt;/p&gt;
&lt;p&gt;
And so, &lt;a href=&quot;https://github.com/elixir-lang/elixir/pull/6508&quot;&gt;a pull request was created&lt;/a&gt; and approved shortly after.&lt;/p&gt;
&lt;h2&gt;
Sharing is Caring&lt;/h2&gt;
&lt;p&gt;
The bulk of Crawler as well as the entirety of OPQ were built in the past month or so. I hope some people will benefit from having these libraries around. And, I hope people also enjoyed me sharing my experience, and perhaps be inspired to start sharing more too.&lt;/p&gt;
&lt;p&gt;
I will leave you all with a Chinese saying: &lt;a href=&quot;https://chinese.yabla.com/chinese-english-pinyin-dictionary.php?define=%E6%BB%B4%E6%B0%B4%E7%A9%BF%E7%9F%B3&quot;&gt;滴水石穿&lt;/a&gt;. The literal translation is “dripping water penetrates the stone”, and what it means is “&lt;strong&gt;constant perseverance yields success&lt;/strong&gt;“.&lt;/p&gt;
]]&gt;</content>
    <published>2017-08-27T11:21:01.000000Z</published>
    <category label="Blog" scheme="http://persumi.com/u/fredwu/tech/e/blog" term="blog"/>
    <category label="Tech" scheme="http://persumi.com/u/fredwu/tech" term="tech"/>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/coding-and-learning-should-never-stop-open-sourcing-is-caring"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/coding-and-learning-should-never-stop-open-sourcing-is-caring</id>
    <title>Coding and Learning Should Never Stop, Open Sourcing is Caring</title>
    <updated>2017-08-27T11:21:01.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;h2&gt;
Preface&lt;/h2&gt;
&lt;p&gt;
If memory serves right, it’s been several years since I first dabbled in Elixir, but it was about a year ago I really started putting some serious effort into learning Elixir, and as a result I made two libraries in the machine learning space: &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;, a Naive Bayes text classifier implementation, and &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt;, an English (Porter2) stemming implementation.&lt;/p&gt;
&lt;p&gt;
Unfortunately after I’ve released those two libraries, I hadn’t had much opportunities to work with Elixir. My day jobs have been mostly Ruby, JavaScript, PHP and a dash of Golang. And so, after being silent for a year, I’ve decided to pick up something I had started a year ago - &lt;a href=&quot;https://github.com/fredwu/crawler&quot;&gt;a web Crawler&lt;/a&gt;. If you are new to Elixir, feel free to follow this project as I am actively developing it.&lt;/p&gt;
&lt;h2&gt;
Learnings&lt;/h2&gt;
&lt;p&gt;
The preface is to give a bit of background of when and how I started learning Elixir, now, let me talk about one of my favourite features of Elixir, and how it helps me write better code not just in Elixir, but in virtually any other language.&lt;/p&gt;
&lt;p&gt;
Introducing the topic of today, a really simple feature, and in fact it has been part of Python for years - the &lt;a href=&quot;https://en.wikipedia.org/wiki/Doctest&quot;&gt;doctest&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
Doctest&lt;/h2&gt;
&lt;p&gt;
In short, a doctest is pieces of code examples that run as part of the test suite, and show up as part of the documentation. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule Greeting do
  @doc &quot;&quot;&quot;
  ## Examples

      iex&gt; hello(&quot;world&quot;)
      &quot;hello world&quot;

      iex&gt; hello(&quot;dear&quot;)
      &quot;hello dear&quot;
  &quot;&quot;&quot;
  def hello(input) do
    &quot;hello #{input}&quot;
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And then all you need is in your corresponding test file, to enable doctest:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule GreetingTest do
  use ExUnit.Case

  doctest Greeting
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Even though I knew about Python’s doctest for years, I’d never realised how impactful it can be given its simplistic nature, probably due to the fact that I’ve never used Python for anything serious.&lt;/p&gt;
&lt;p&gt;
Until Elixir.&lt;/p&gt;
&lt;p&gt;
There are three things I find the most impactful as I write more doctests: clarity, scope, and design.&lt;/p&gt;
&lt;h2&gt;
Clarity&lt;/h2&gt;
&lt;p&gt;
As a ruby programmer, I appreciate greatly the beauty of not just the main code base, but also its test suite. Hence I’ve always preferred to use RSpec in larger code bases. However, as the application gets more complex, and as the number of test files grows, the cognitive overhead of reading and processing all the files and lines becomes higher and higher.&lt;/p&gt;
&lt;p&gt;
Doctest solves this perfectly - no longer do we have to crawl through the right file and the right line for a particular test case, all the test cases are neatly presented right in front of you as you read the function itself.&lt;/p&gt;
&lt;p&gt;
You might think this as trivial, but just like many organisations spend time and effort to optimise for effective communication by studying &lt;a href=&quot;https://en.wikipedia.org/wiki/Proxemics&quot;&gt;proxemics&lt;/a&gt;, proxemics between different components of a software code base also plays a role in improving the code clarity, and ultimately the code quality.&lt;/p&gt;
&lt;h2&gt;
Scope&lt;/h2&gt;
&lt;p&gt;
Doctest is purposely simple, and is designed for unit tests. There have been many times when I found myself realising my function was too dependant on external states, or are doing too many things because it was hard to write simple doctests. In a way, the constraints of doctests have forced me to rethink the scope of my function, and that would often lead to an overall better designed system.&lt;/p&gt;
&lt;h2&gt;
Design&lt;/h2&gt;
&lt;p&gt;
As much as I’d like to think about the &lt;a&gt;SOLID&lt;/a&gt; principles all the time, it is often too easy to dig deep wholes in the midst of building things.&lt;/p&gt;
&lt;p&gt;
Every now and then I find myself extracting a piece of logic to a private function and calling it a day. In Elixir, only public functions can have doctests - again, this constraint pushes you to think about the importance and the role of a particular function, perhaps it is better to be moved to another module as a public function therefore can have its own doctests. Here is &lt;a href=&quot;https://github.com/fredwu/crawler/commit/7ac06b40a6c75b5e01311e70710185011f82471f&quot;&gt;an example&lt;/a&gt; when I did some refactorings on Crawler.&lt;/p&gt;
&lt;h2&gt;
Closing&lt;/h2&gt;
&lt;p&gt;
I wanted to write this article for a while now - as I truly love and appreciate Elixir’s asthetics and features. Many developers might find functional programming as a barrier, but I can assure you that with Elixir’s tooling and ecosystem, and of course doctest (&lt;em&gt;wink&lt;/em&gt;), building software feels like a breeze.&lt;/p&gt;
&lt;p&gt;
Last time when I was this happy building software was when I first discovered ruby. If you haven’t given Elixir a try yet, I encourage you to do so sooner rather than later, it will not only give you a functional programming perspective, but will also help you write better code in other languages.&lt;/p&gt;
]]&gt;</content>
    <published>2017-08-07T10:00:20.000000Z</published>
    <category label="Blog" scheme="http://persumi.com/u/fredwu/tech/e/blog" term="blog"/>
    <category label="Tech" scheme="http://persumi.com/u/fredwu/tech" term="tech"/>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/elixir-and-doctest-help-writing-better-programs-one-function-at-a-time"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/elixir-and-doctest-help-writing-better-programs-one-function-at-a-time</id>
    <title>Elixir and Doctest - Help Writing Better Programs, One Function At A Time</title>
    <updated>2017-08-07T10:00:20.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
About a month ago I was in-between jobs - I had two weeks to rest up, recharge and get ready for my new job. So I thought,&amp;nbsp;I should use those two weeks to learn something new.&lt;/p&gt;
&lt;p&gt;
Years ago I briefly looked into &lt;a href=&quot;http://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt; when it was first released to the wild, at the time I wasn’t interested in picking it up due to its syntax similarity to Ruby, despite their vastly different underlying semantics. I love Ruby, and it’s been my weapon of choice for the past 6-7 years, so when it came time for me to learn something &lt;em&gt;new&lt;/em&gt;, I naturally wanted to learn something a bit more different than Ruby, syntax-wise.&lt;/p&gt;
&lt;p&gt;
Fast-forward a few years, I am more mature and open-minded, and are now in a position to welcome Elixir and to embrace the Ruby-like syntax as well as the functional programming mindset with open arms.&lt;/p&gt;
&lt;h2&gt;
Learning Elixir&lt;/h2&gt;
&lt;p&gt;
Given the strong influence of Ruby in Elixir, I can’t help but get nostalgic about learning it by reading another great book by Dave Thomas: &lt;a href=&quot;https://pragprog.com/book/elixir12/programming-elixir-1-2&quot;&gt;Programming Elixir&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Dave Thomas’ original &lt;a href=&quot;https://pragprog.com/book/ruby/programming-ruby&quot;&gt;Programming Ruby&lt;/a&gt; was instrumental in the success of the Ruby programming language and its communities in the west, and it certainly has helped me to greatly widen my exposure to not only the wonderful world of Ruby but object-oriented programming in general as a PHP developer.&lt;/p&gt;
&lt;p&gt;
Learning Elixir has been really fun, not only does it share the same developer-friendliness championed by Ruby, it also does so with remarkable strength in concurrency thanks to BEAM (Erlang’s VM).&lt;/p&gt;
&lt;p&gt;
Many people are drawn to Elixir due to its Ruby influence, its functional and immutability nature, and its &lt;a href=&quot;https://en.wikipedia.org/wiki/Actor_model&quot;&gt;Actor-based concurrency model&lt;/a&gt;. I would however like to call out one of my favourite features of Elixir that sometimes gets overlooked, and that is how you could write unit tests using &lt;a href=&quot;http://elixir-lang.org/docs/stable/ex_unit/ExUnit.DocTest.html&quot;&gt;&lt;code class=&quot;inline&quot;&gt;ExUnit.DocTest&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Elixir Makes Writing Unit Tests Effortless&lt;/h3&gt;
&lt;p&gt;
Python programmers have been writing doctests for years, but Ruby due to not having an equivalent standard library, has never had writing doctests taken off in its community. I’m glad Elixir has.&lt;/p&gt;
&lt;p&gt;
Take a look at the code snippet below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule Stemmer.Step0 do
  @doc &quot;&quot;&quot;
  ## Examples

      iex&gt; Stemmer.Step0.trim_apostrophes(&quot;&apos;ok&quot;)
      &quot;ok&quot;

      iex&gt; Stemmer.Step0.trim_apostrophes(&quot;o&apos;k&quot;)
      &quot;o&apos;k&quot;

      iex&gt; Stemmer.Step0.trim_apostrophes(&quot;&apos;o&apos;k&apos;&quot;)
      &quot;o&apos;k&quot;
  &quot;&quot;&quot;
  def trim_apostrophes(word) do
    word
    |&gt; String.replace_prefix(&quot;&apos;&quot;, &quot;&quot;)
    |&gt; String.replace_suffix(&quot;&apos;&quot;, &quot;&quot;)
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The three test cases given will actually be tested by ExUnit, provided you ask it to do so as below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule Stemmer.Step0Test do
  use ExUnit.Case, async: true

  doctest Stemmer.Step0
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
You may think having doctests is not a big deal, what I can say is that I am really enjoying the fact that &lt;strong&gt;unit tests can be written with minimal friction and high visibility&lt;/strong&gt; (as they sit with the implementation, rather than within a big test suite with a sea of files). Besides, not all of us practice TDD religiously or 100% of the time, and when I don’t or can’t do TDD, having doctests ensures I don’t forget adding test cases. :)&lt;/p&gt;
&lt;h3&gt;
Toy Robot in Elixir&lt;/h3&gt;
&lt;p&gt;
The best way to learn something new is to practice it as you learn it. Instead of diving straight into a complicated system, or to write a &lt;code class=&quot;inline&quot;&gt;hello world&lt;/code&gt; program, I thought I would dig out a code test and re-implement it in Elixir.&lt;/p&gt;
&lt;p&gt;
The code test is the rather infamous &lt;a href=&quot;https://github.com/search?q=toy+robot&quot;&gt;Toy Robot&lt;/a&gt; test. As an interviewer, I must have reviewed a few hundreds of these tests, most of which in Ruby. I know what a good OO solution looks like, so naturally I was looking forward to “rethink” the problem and have a crack at it using Elixir.&lt;/p&gt;
&lt;p&gt;
Here is the result: &lt;a href=&quot;https://github.com/fredwu/toy-robot-elixir&quot;&gt;https://github.com/fredwu/toy-robot-elixir&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
As a learner, I wanted to practice as many language features as possible, so included in the Toy Robot test code I tried:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Different data structures such as Struct and List  &lt;/li&gt;
  &lt;li&gt;
Pattern matching  &lt;/li&gt;
  &lt;li&gt;
Data piping (&lt;code class=&quot;inline&quot;&gt;|&gt;&lt;/code&gt;)  &lt;/li&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;Agent&lt;/code&gt; for managing states  &lt;/li&gt;
  &lt;li&gt;
Macros for validation rules  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
I kept the test code small and nimble on purpose to illustrate the readability of a highly expressive language. I may not have exercised all the language features (such as supervisors and protocols), but at that point I felt I could start the Elixir journey with a big smile on my face already.&lt;/p&gt;
&lt;h2&gt;
Learning Phoenix&lt;/h2&gt;
&lt;p&gt;
I have to admit, a big part of the reason that got me started on learning Elixir is to build a side project. Initially I wanted to simply use Ruby and Rails because I can be very productive using them, but in the end I decided on learning Elixir and &lt;a href=&quot;http://www.phoenixframework.org/&quot;&gt;Phoenix&lt;/a&gt;, because &lt;strong&gt;most side projects fail, and when they do, how much learning can you extract out of those experiences?&lt;/strong&gt; My answer to this question, is to learn a new language, a new framework and most importantly a new programming paradigm to drastically increase the amount of learning I could gain.&lt;/p&gt;
&lt;p&gt;
But please, before you jump on Phoenix, learn Elixir properly first! Over the years I’ve seen far too many cases of people jumping on Rails without understanding the language features of Ruby…&lt;/p&gt;
&lt;p&gt;
To learn Phoenix, the &lt;a href=&quot;https://pragprog.com/book/phoenix/programming-phoenix&quot;&gt;Programming Phoenix&lt;/a&gt; book is pretty much the bible on this subject aside from the official guide, and it is written by Chris McCord, the author of Phoenix, Bruce Tate, and Jose Valim, the author of Elixir.&lt;/p&gt;
&lt;p&gt;
As an experienced Rails developer, it only took me a day or two to go through the book (I largely skimmed the chapters on &lt;code class=&quot;inline&quot;&gt;Channels&lt;/code&gt; as I don’t intend to build a real-time app).&lt;/p&gt;
&lt;p&gt;
Now, as a Ruby developer, the biggest pain point when working on Rails or other sizeable Ruby MVC projects for me, is ActiveRecord. I got so frustrated to the status quo I even &lt;a href=&quot;https://github.com/fredwu/datamappify&quot;&gt;attempted to fix it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Fortunately, in Elixir and Phoenix we have &lt;a href=&quot;https://github.com/elixir-ecto/ecto&quot;&gt;Ecto&lt;/a&gt;. One of my favourite features of Ecto is the concept of &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html&quot;&gt;Changeset&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Take a look at the code snippet below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule MySecretApp.User do
  use MySecretApp.Web, :model

  schema &quot;users&quot; do
    field :username, :string
    field :email, :string
    field :password, :string, virtual: true
    field :encrypted_password, :string

    has_one :profile, MySecretApp.Profile

    timestamps()
  end

  def changeset(struct, params \\ %{}) do
    struct
    |&gt; cast(params, [:username, :email])
    |&gt; validate_required([:username, :email])
    |&gt; validate_length(:username, min: 3, max: 20)
    |&gt; validate_format(:username, ~r/\A[a-zA-Z0-9_]+\z/, message: &quot;alphanumeric and underscores only&quot;)
    |&gt; validate_format(:email, ~r/@/)
    |&gt; unique_constraint(:username)
    |&gt; unique_constraint(:email)
  end

  def creation_changeset(struct, params \\ %{}) do
    struct
    |&gt; changeset(params)
    |&gt; password_changeset(params)
  end


  defp password_changeset(struct, params) do
    struct
    |&gt; cast(params, [:password])
    |&gt; validate_required([:password])
    |&gt; validate_length(:password, min: 8, max: 200)
    |&gt; encrypt_password
  end

  defp encrypt_password(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: password}} -&gt;
        put_change(changeset, :encrypted_password, Comeonin.Bcrypt.hashpwsalt(password))
      _ -&gt;
        changeset
    end
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The &lt;code class=&quot;inline&quot;&gt;changset/2&lt;/code&gt; function can be used when a user needs to be updated, whereas the &lt;code class=&quot;inline&quot;&gt;creation_changeset/2&lt;/code&gt; is to be used only when a user is first created. Sure, you can achieve similar result in Rails by using custom validators, but the fact that this practice is enforced by the library and the framework, is encouraging.&lt;/p&gt;
&lt;p&gt;
One other thing that I’ve seen in larger Rails apps, is the leaky abstraction of view-level logic, they typically sit in controllers, helpers (which are globally available) or worse, models. Phoenix in this case follows what &lt;a href=&quot;http://hanamirb.org/&quot;&gt;Hanami&lt;/a&gt;, &lt;a href=&quot;http://trailblazer.to/&quot;&gt;Trailblazer&lt;/a&gt; and many other frameworks do: it introduces a “view model” layer.&lt;/p&gt;
&lt;p&gt;
Something along the lines of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule MySecretApp.UserView do
  use MySecretApp.Web, :view

  def full_name do
    &quot;#{title} #{first_name} #{last_name}&quot;
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
In a nutshell, Phoenix is just like Rails, but less magical (in a good way) and faster. :) Like the Elixir eco-system, most libraries and concepts feel like their ruby/rails counterparts, but more refined.&lt;/p&gt;
&lt;p&gt;
There are a few popular &lt;a href=&quot;https://hex.pm/&quot;&gt;Hex&lt;/a&gt; packages if you’re familiar with their ruby counterparts:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left;&quot;&gt;
      &lt;/th&gt;
      &lt;th style=&quot;text-align: left;&quot;&gt;
Elixir      &lt;/th&gt;
      &lt;th style=&quot;text-align: left;&quot;&gt;
Ruby      &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Code analysis      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/credo&quot;&gt;credo&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/rubocop&quot;&gt;rubocop&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Testing      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/espec&quot;&gt;espec&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/rspec&quot;&gt;rspec&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Browser testing      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/wallaby&quot;&gt;wallaby&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/capybara&quot;&gt;capybara&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Test coverage      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/excoveralls&quot;&gt;excoveralls&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/simplecov&quot;&gt;simplecov&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Test factory      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/ex_machina&quot;&gt;ex_machina&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/factory_girl&quot;&gt;factory_girl&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Authentication      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/guardian&quot;&gt;guardian&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/devise&quot;&gt;devise&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;
And the list goes on and on… Be sure to check out &lt;a href=&quot;https://github.com/h4cc/awesome-elixir/&quot;&gt;Awesome Elixir&lt;/a&gt; for more community curated Elixir libraries.&lt;/p&gt;
&lt;h2&gt;
Learning Machine Learning&lt;/h2&gt;
&lt;p&gt;
As I started building the foundation of my side project, you know, the usual user management and session management, etc, etc, by chance I came across mentions of &lt;a href=&quot;https://en.wikipedia.org/wiki/Bayesian_inference&quot;&gt;Bayesian inference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
One thing led to another, very soon I started looking into varies different machine learning algorithms such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Naive_Bayes_classifier&quot;&gt;Naive Bayes&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Random_forest&quot;&gt;Random Forest&lt;/a&gt;. These are all subjects I had never come across or even heard of before, as I have no strong mathematics, statistics or computer science background. I was however, intrigued and inspired nonetheless, and wanted to employ some machine learning in my side project.&lt;/p&gt;
&lt;p&gt;
And the best way to understand and learn about machine learning algorithms? You guessed, is to write one! After some research, I came to the conclusion that Naive Bayes is one of the simplest to implement, is great for text classification which is useful for my side project, and has good accuracy given its simplicity and fast speed.&lt;/p&gt;
&lt;h3&gt;
Introducing &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
So, after a few train rides to and from work, on my newly purchased Macbook (yes, the one with only a single USB-C port), I built a Naive Bayes library: &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
As of writing, this is the only Naive Bayes library in Elixir that supports the following features:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Naive Bayes algorithm with different models    &lt;ul&gt;
      &lt;li&gt;
Multinomial      &lt;/li&gt;
      &lt;li&gt;
Binarized (boolean) multinomial      &lt;/li&gt;
      &lt;li&gt;
Bernoulli      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
No external dependencies  &lt;/li&gt;
  &lt;li&gt;
Ignores stop words  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Additive_smoothing&quot;&gt;Additive smoothing&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Tf-idf&quot;&gt;TF-IDF&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
Optional keywords weighting  &lt;/li&gt;
  &lt;li&gt;
Optional word stemming via &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Let’s see it in action shall we? :)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;SimpleBayes.init
|&gt; SimpleBayes.train(:ruby, &quot;I enjoyed using Rails and ActiveRecord for the most part.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;The Ruby community is awesome.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;There is a new framework called Hanami that&apos;s promising.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;Please learn Ruby before you learn Rails.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;We use Rails at work.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;It has Phoenix which is a Rails-like framework.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;Its author is a Rails core member, Jose Valim.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;Phoenix and Rails are on many levels, comparable.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;Phoenix has great performance.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;I love Elixir.&quot;)
|&gt; SimpleBayes.train(:php, &quot;I haven&apos;t written any PHP in years.&quot;)
|&gt; SimpleBayes.train(:php, &quot;The PHP framework Laravel is inspired by Rails.&quot;)
|&gt; SimpleBayes.classify(&quot;I wrote some Rails code at work today.&quot;)
# =&gt; [
# ruby: 0.20761437345986136,
# elixir: 0.08101868169313056,
# php: 0.019047884912605735
# ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
As you can see, provided with reasonable training data, Naive Bayes can work extremely well.&lt;/p&gt;
&lt;h3&gt;
Introducing &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
Something I discovered as I was building Simple Bayes, is something called &lt;a href=&quot;https://en.wikipedia.org/wiki/Stemming&quot;&gt;stemming&lt;/a&gt;. Let’s see another example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;SimpleBayes.init(stem: false)
|&gt; SimpleBayes.train(:apple, &quot;buying apple&quot;)
|&gt; SimpleBayes.train(:banana, &quot;buy banana&quot;)
|&gt; SimpleBayes.classify(&quot;buy apple&quot;)
# =&gt; [
# banana: 0.057143654737817205,
# apple: 0.057143654737817205
# ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Oops, the probabilities of the sentence “buy apple” of being &lt;code class=&quot;inline&quot;&gt;apple&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;banana&lt;/code&gt; are the same, that’s not good, as we know “buying” and “buy” should mean the same thing. This is where stemming comes in handy. Let’s enable stemming and run the example again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;SimpleBayes.init(stem: true)
|&gt; SimpleBayes.train(:apple, &quot;buying apple&quot;)
|&gt; SimpleBayes.train(:banana, &quot;buy banana&quot;)
|&gt; SimpleBayes.classify(&quot;buy apple&quot;)
# =&gt; [
# apple: 0.18096114003107086,
# banana: 0.1505149978319906
# ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Stemming in this case correctly identified “buy” and “buying” as the same thing (they both have the stemmed root of “buy”).&lt;/p&gt;
&lt;p&gt;
How did I include stemming in Simple Bayes you wonder? Let me introduce you to &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt; - as of writing this is the only Porter2 stemmer available in Elixir. :)&lt;/p&gt;
&lt;p&gt;
Elixir and BEAM may not be known for their raw performance, but compared to a similar Porter2 stemmer implemented in pure ruby, my Elixir version runs about five times faster (under &lt;code class=&quot;inline&quot;&gt;5&lt;/code&gt;s to stem 29000+ words, compared to &lt;code class=&quot;inline&quot;&gt;25&lt;/code&gt;s with the ruby version).&lt;/p&gt;
&lt;h2&gt;
The Learning Continues…&lt;/h2&gt;
&lt;p&gt;
I’ve only been writing Elixir for a month, and there are still heaps to learn and to practice. So far though, I have to say my experience with both Elixir and Phoenix has been fantastic - I get the same satisfying and pumped feeling I got when I was learning Ruby and Rails years ago.&lt;/p&gt;
&lt;p&gt;
If you haven’t checked out Elixir, I strongly encourage you to do so, after all, the world seems to be moving in the concurrency direction at an increasingly rapid speed, including &lt;a href=&quot;http://engineering.appfolio.com/appfolio-engineering/2015/11/18/ruby-3x3&quot;&gt;Ruby 3x3&lt;/a&gt;. :)&lt;/p&gt;
]]&gt;</content>
    <published>2016-07-23T18:33:29.000000Z</published>
    <category label="Blog" scheme="http://persumi.com/u/fredwu/tech/e/blog" term="blog"/>
    <category label="Tech" scheme="http://persumi.com/u/fredwu/tech" term="tech"/>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/i-accidentally-some-machine-learning-my-story-of-a-month-of-learning-elixir"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/i-accidentally-some-machine-learning-my-story-of-a-month-of-learning-elixir</id>
    <title>I Accidentally Some Machine Learning - My Story of A Month of Learning Elixir</title>
    <updated>2016-07-23T18:33:29.000000Z</updated>
  </entry>
</feed>