Skip to content

Commit 27a6133

Browse files
chore: generate HTML pages from markdown
1 parent 8e72f65 commit 27a6133

File tree

6 files changed

+467
-4
lines changed

6 files changed

+467
-4
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Hey, I'm Vidal Vasconcelos
22

3-
I’ve been coding for over 10 years at companies of all sizes, driven by a love for
4-
software development. I’ve had the chance to work with Backend, Frontend, and
5-
Infrastructure technologies, always aiming to build reliable software and foster
6-
strong, healthy development teams.
3+
For over 10 years, I’ve been coding at companies of all sizes, driven by a deep passion for software development. Along
4+
the way, I’ve had the chance to work with Backend, Frontend, and Infrastructure technologies, always focusing on
5+
building reliable software and fostering strong, healthy development teams.
76

87
## Last updates
8+
99
- [Composing sorting rules with fp-ts](./posts/2025-10-10-COMPOSE-SORTING-RULES.md)
1010
- [A bit more contramap](posts/2025-10-23-A-BIT-MORE-CONTRAMAP.md)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>A Summary of a Year with B2rent</title>
6+
<link href="./assets/styles.css" rel="stylesheet">
7+
</head>
8+
<body>
9+
<article>
10+
<p><a href="./index.html">&larr; Back to home</a></p>
11+
<h1>A Summary of a Year with B2rent</h1>
12+
<p><strong>Date:</strong> July 23, 2018</p>
13+
<p>About a year and many cups of coffee ago, I was invited to work on a project in Fortaleza. That was what finally
14+
made me move to a new city—something I had been postponing for two years, insisting on staying in Sobral.</p>
15+
<p>The project was incredible: working on a Laravel 5.1 application aimed at helping companies manage rentals.
16+
Initially, I was going to be the sole developer leading this endeavor, with B2rent being just one of the
17+
products of Pilps GP.</p>
18+
<p>I felt like the perfect developer for the job. Working with legacy code in a startup environment had become
19+
almost second nature over the past two years. That period—working as a freelancer or building my own startup—was
20+
one of the best times of my life up to that point. I met many people who helped me and radically changed my
21+
worldview.</p>
22+
<p>That energy of creating new things and learning every second is an intrinsic part of who I am today. The
23+
challenge was to expand the project's functionalities while streamlining the codebase—a Herculean task, to say
24+
the least.</p>
25+
<p>A prime example of this is that, even a year later, there are still Controllers I have never touched. The reason
26+
for the bloated codebase was the number of developers who had worked on it before me, implementing isolated
27+
features without considering maintainability—each with a different vision of what the project should be.</p>
28+
<p>Most tasks always came with extensive refactoring of the codebase: removing business logic from Controllers,
29+
optimizing massive queries, extracting useless code, and, more often than not, negotiating scope adjustments.
30+
Through this, I learned that much of what makes software complex is the lack of structured processes on the
31+
client’s end.</p>
32+
<p>The result of this work can be seen in the following graph, which shows the number of lines added and removed in
33+
my contributions to B2rent. These numbers aren’t what truly matter—the key takeaway is that, even as the product
34+
gained more features, we managed to keep the codebase as lean as possible.</p>
35+
<table>
36+
<thead>
37+
<tr>
38+
<th>Commits</th>
39+
<th>Lines Added</th>
40+
<th>Lines Removed</th>
41+
</tr>
42+
</thead>
43+
<tbody>
44+
<tr>
45+
<td>404</td>
46+
<td>603,784</td>
47+
<td>1,875,203</td>
48+
</tr>
49+
</tbody>
50+
</table>
51+
<p>The frontend was another challenge. Laravel comes with a built-in structure for Vue.js 1.0, but that setup had
52+
been completely removed at the project's start.</p>
53+
<p>Since I was particularly a fan of the library, one of my first tasks was to recreate its setup. Although Vue 2.0
54+
had already been announced at the time, bringing some incompatibilities, we built most of our components with
55+
the new version’s requirements in mind.</p>
56+
<p>The goal was to reduce the percentage of jQuery and plain HTML while increasing Vue’s presence by creating
57+
components of various parts of the application. We also aimed to remove AdminLTE from our app. I have nothing
58+
against templates like that, but if you don’t want to give the impression that you’re just another generic app,
59+
don’t use them.</p>
60+
<p>Another lesson I learned is that, to scale, you need a simple, agile, and flexible structure. This has made me
61+
rethink the use of frameworks, but that’s a topic for another post. We evolved from a bloated, static admin
62+
template to a lean and efficient app.</p>
63+
<p>This wasn’t just my work alone, but I’m proud to have led this project, worked on its identity, and helped design
64+
every feature we built. This post may feel like a farewell. After a year of living and breathing this project,
65+
so much has happened.</p>
66+
<p>Our team has grown, and other projects within Pilps have also expanded, demanding more of my time. Currently, I’m
67+
working full-time with TypeScript, and this shift has motivated me to write more. In a future post, I want to
68+
share how we migrated our frontend to Vue.js 2 + TypeScript and how this transition helped balance knowledge
69+
across all our projects.</p>
70+
</article>
71+
</body>
72+
</html>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Composing sort rules using fp-ts</title>
6+
<link href="./assets/styles.css" rel="stylesheet">
7+
</head>
8+
<body>
9+
<article>
10+
<p><a href="./index.html">&larr; Back to home</a></p>
11+
<h1>Composing sort rules using fp-ts</h1>
12+
<p><strong>Date:</strong> October 10, 2025</p>
13+
<p>In this example I want to double down on the power of composition: the power of assembling complex rules from
14+
smaller, well-understood pieces and how that helps us define better boundaries between our modules. In this
15+
walkthrough we are using plain files, but in a more realistic setup each piece could happily live inside its own
16+
module in different directories.</p>
17+
<p>So let's start with a very common scenario that shows up across different kinds of applications: categories and
18+
products, and the rules that govern how we sort them for display.</p>
19+
<p>Our first step is to list the tools we have on hand so we keep our code cohesive without reinventing the wheel.
20+
For that we reach for the <code>Ord</code> module from fp-ts<sup><a href="#fn1">[1]</a></sup> along with a
21+
couple of essentials such as Monoids.</p>
22+
<p>This module lets us express ordering rules declaratively. Contrast that with what we usually find in
23+
applications: ad-hoc sorting logic scattered all over the place, reproduced multiple times, full of ternaries
24+
and <code>if</code>/<code>else</code> statements. Things get gnarly the moment complex, nested rules arrive, and
25+
maintaining or extending that code quickly becomes a nightmare.</p>
26+
<p>Enough &ldquo;lero-lero&rdquo;, let's see how this works in practice. First, let's lock down our acceptance
27+
criteria:</p>
28+
<pre><code class="language-markdown">**0001: Products must be ordered according to these rules:**
29+
30+
1. Products must be sorted alphabetically by their `name`.
31+
2. Products that belong to the same category must stay together.
32+
3. Product categories must be ordered alphabetically by their `name`.
33+
</code></pre>
34+
<p>With that in mind we can start modeling the problem we want to solve. We face two entities: Product and Category.
35+
In this example the relationship is 1:1. We'll introduce them with interfaces, which lets us reuse them freely
36+
while abstracting away any concrete implementation details that do not matter right now.</p>
37+
<pre><code class="language-ts">interface Category {
38+
id: string
39+
name: string
40+
}
41+
42+
interface Product {
43+
id: string
44+
name: string
45+
category: Category
46+
unavailable: boolean
47+
}
48+
</code></pre>
49+
<p>Now we can tackle each criterion in isolation.</p>
50+
<p>&ldquo;Product categories must be ordered alphabetically by their <code>name</code> attribute.&rdquo;</p>
51+
<p>We can read that as: a product entity has to follow the exact same ordering rule as its <code>name</code>
52+
property, which is a <code>string</code>. Great, we already know how to sort strings. Take a look at this piece
53+
of documentation from the fp-ts <code>string</code> module<sup><a href="#fn2">[2]</a></sup>:</p>
54+
<pre><code class="language-ts">import * as s from 'fp-ts/string'
55+
56+
assert.deepStrictEqual(s.Ord.compare('a', 'a'), 0)
57+
assert.deepStrictEqual(s.Ord.compare('a', 'b'), -1)
58+
assert.deepStrictEqual(s.Ord.compare('b', 'a'), 1)
59+
</code></pre>
60+
<p>We can leverage <code>contramap</code> to hand that same behavior to both Category and Product. To keep things
61+
short, let's focus on Category first.</p>
62+
<pre><code class="language-ts">import * as s from 'fp-ts/string'
63+
64+
const ordCategoriesAlphabetically: Ord.Ord&lt;Category&gt; = pipe(
65+
s.Ord,
66+
Ord.contramap((category: Category): string =&gt; category.name),
67+
)
68+
</code></pre>
69+
<p>Now we have an <code>Ord&lt;Category&gt;</code> (and, by extension, an <code>Ord&lt;Product&gt;</code>) that
70+
partially solves criteria 1 and 3. Next we have to handle criteria 2, which we can rephrase as: &ldquo;Products
71+
must be ordered by category.&rdquo; Once again we face the same shape of problem, we want a type to follow the
72+
ordering of one of its internal attributes. So we can reuse the exact same strategy:</p>
73+
<pre><code class="language-ts">import * as s from 'fp-ts/string'
74+
75+
const ordProductsAlphabeticallyByCategory: Ord.Ord&lt;Product&gt; = pipe(
76+
s.Ord,
77+
Ord.contramap((product: Product) =&gt; product.category.name),
78+
)
79+
</code></pre>
80+
<p>Notice that while this works, we can simplify further because we already defined what it means to order
81+
categories by <code>name</code>. That gives us a healthier coupling between the two modules:</p>
82+
<pre><code class="language-ts">const ordProductsAlphabeticallyByCategory: Ord.Ord&lt;Product&gt; = pipe(
83+
ordCategoriesAlphabetically,
84+
Ord.contramap((product: Product) =&gt; product.category),
85+
)
86+
</code></pre>
87+
<p>Although we've addressed each requirement individually, and in a reusable way that extends beyond the original
88+
problem, we still need a mechanism to combine these rules so they deliver the result we actually want. This is
89+
where a remarkably powerful tool steps in: the Monoid<sup><a href="#fn3">[3]</a></sup>.</p>
90+
<p>Yes, the name sounds odd, but there's no reason to panic. Think of Monoids as pressure cookers: surprisingly easy
91+
to operate and capable of making your life a lot simpler.</p>
92+
<pre><code class="language-ts">import * as M from 'fp-ts/Monoid'
93+
94+
const ordProducts: Ord.Ord&lt;Product&gt; = M.concatAll(Ord.getMonoid())([
95+
ordProductsAlphabeticallyByCategory,
96+
ordProductsAlphabeticallyByName,
97+
])
98+
</code></pre>
99+
<p>If currying is not your daily driver, we can rewrite the solution like this:</p>
100+
<pre><code class="language-ts">import * as M from 'fp-ts/Monoid'
101+
102+
const combineMultipleOrds = M.concatAll(Ord.getMonoid())
103+
104+
const ordProducts: Ord.Ord&lt;Product&gt; = combineMultipleOrds([
105+
ordProductsAlphabeticallyByCategory,
106+
ordProductsAlphabeticallyByName,
107+
])
108+
</code></pre>
109+
<p>The result is still an <code>Ord&lt;Product&gt;</code>, but now each criteria lives in isolation, making
110+
maintenance equally isolated. The implementation also becomes a semantic entry point for understanding the
111+
sorting rules. By combining them in a list we get a ranking view where we can quickly see which rule takes
112+
precedence. We can toggle them on or off with nothing more than a comment.</p>
113+
<p>Let's push this system a bit further by introducing a hypothetical feature. Imagine Products now have an <code>unavailable</code>
114+
boolean flag and a new acceptance criteria:</p>
115+
<pre><code class="language-markdown">**0002: Products must be ordered according to these rules:**
116+
117+
1. They must follow the rules defined in #0001.
118+
2. Products marked as unavailable must appear at the end of their category list.
119+
</code></pre>
120+
<p>Following the same approach, we add a new <code>Ord&lt;Product&gt;</code> and include it alongside the existing
121+
rules:</p>
122+
<pre><code class="language-ts">import * as b from "fp-ts/boolean";
123+
124+
const ordProductsByUnavailability: Ord.Ord&lt;Product&gt; = pipe(
125+
b.Ord,
126+
Ord.contramap((product: Product): boolean =&gt; product.unavailable),
127+
)
128+
129+
// ...
130+
131+
const ordProducts: Ord.Ord&lt;Product&gt; = M.concatAll(Ord.getMonoid())([
132+
ordProductsAlphabeticallyByCategory,
133+
ordProductsByUnavailability,
134+
ordProductsAlphabeticallyByName,
135+
])
136+
</code></pre>
137+
<p>Stop the machines! The users started complaining, the unavailable products buried the items they actually wanted
138+
to see. A new requirement pops up:</p>
139+
<pre><code class="language-markdown">**0003: Products must be ordered according to these rules:**
140+
141+
1. This requirement deprecates #0002.
142+
</code></pre>
143+
<p>Done. We just disable the availability rule:</p>
144+
<pre><code class="language-ts">const ordProducts: Ord.Ord&lt;Product&gt; = M.concatAll(Ord.getMonoid())([
145+
ordProductsAlphabeticallyByCategory,
146+
// ordProductsByUnavailability,
147+
ordProductsAlphabeticallyByName,
148+
])
149+
</code></pre>
150+
<h2>Conclusion</h2>
151+
<p>Composition keeps our sorting logic friendly and ready for change. Each rule stays readable, the intent is
152+
obvious, and we can reuse behavior or toggle features with a quick comment. Monoids are still that pressure
153+
cooker from earlier: grab the handle, let them do the heavy lifting, and serve the result with zero stress. The
154+
next time someone asks for a fresh sorting tweak, just add it to the list and count on the combined rule set to
155+
keep everything tidy.</p>
156+
<hr>
157+
<section id="footnotes">
158+
<h3>Footnotes</h3>
159+
<ol>
160+
<li id="fn1"><a href="https://github.com/gcanti/fp-ts">https://github.com/gcanti/fp-ts</a></li>
161+
<li id="fn2"><a href="https://gcanti.github.io/fp-ts/modules/string.ts.html#ord">https://gcanti.github.io/fp-ts/modules/string.ts.html#ord</a>
162+
</li>
163+
<li id="fn3"><a href="https://gcanti.github.io/fp-ts/modules/Monoid.ts.html#monoid-overview">https://gcanti.github.io/fp-ts/modules/Monoid.ts.html#monoid-overview</a>
164+
</li>
165+
</ol>
166+
</section>
167+
</article>
168+
</body>
169+
</html>

0 commit comments

Comments
 (0)