Passive Learning Is Just Entertainment Unless You Follow These Two Strategies

Passive consumption is procrastination in disguise.

If you’re like me, you’ve been watching another YouTube video, reading another book, and consuming online content as procrastination.

Mindless consumption isn’t learning.

Learning should be active. We truly learn when we engage in our learning process. Instead of simply reading or watching pretending to learn, engage with what we’re learning.

Start taking notes by hand

That’s the easiest way to engage with your learning.

Yes, taking notes by hand! When we take notes by hand, we get “physical” like the 80s hit. Touching a pen and feeling the paper create brain connections that reinforce our learning.

Ditch your laptop and go back to pen and paper.

After taking notes, create something new

Taking notes is only the first step.

The best way to truly learn is to create something from what you consume.

Instead of reading to show off a book count, read with intention: answer a question or face a challenge. Once you read, write a summary or explain it to others in your own words.

If you’re a podcast lover, don’t simply listen to the next episode until the end. Write down 3 main points you learned, transcribe your handwritten notes, and share them online.

The next time you find a new programming concept, incorporate that concept into a small project. And, if you’re reading an open-source project, create a simplified version of its main feature to make sure you understand the main decisions and concepts behind it.

And, if you like writing, do copy work. Recreate a piece from a writer you want to imitate. Again by hand. This is an exercise attributed to Benjamin Franklin. After reading a piece, he looked away and tried to rewrite what he read.

Separate the exploration phase where you only gather resources from the engaging phase where you create something new after consuming.

If you listen, you will forget. If you write, you will remember. But if you practice, you won’t forget.

It's Not About Selling. It's About Helping

The other day I had a weird DM exchange.

A guy shared a cool LinkedIn tip. I rushed to apply it and DMed him to say thanks for that cool idea. You know I wanted to make friends in the DM.

He continued the conversation. I shared how my LinkedIn journey was going. I told him about my 100-post experiment and my future plans.

After a couple of exchanges, the guy went on sale mode. He had an offering and I could potentially be one client. I got that!

But, he wrote something in the lines of “Truth is I don’t need more clients and I’m fully booked…“ I stopped reading at that point. If he doesn’t need clients, why he was on salesy mode with that line in the first place?

People don’t like those mind tricks. At least I don’t. Those Jedi mind tricks don’t work on me.

I would have rephrased that message like “hey, maybe you don’t help with <insert service here> yet. Once you do, feel free to shoot me a message, I’d be glad to help you. That’s what I do for a living and I have helped more than <insert client count here> people with that.”

It would have booked a call after a message like that. Or at least I wouldn’t have ghosted him.

It’s not about selling, it’s about helping.

The Power of Function Composition — to Find If an Array Is Special

Passing the result of a function to another isn’t the only way to compose two functions.

From math classes, we’re used to composing two functions like this compose(f, g) = x => f(g(x)). But it turns out there are more ways.

That’s my main takeaway from the talk “The Power of Function Composition” by Conor Hoekstra at NDC Conference 2024.

Here’s the YouTube recording,

The talk shows how to find if an array is special using languages like Python, Haskell, and other obscure and esoteric ones to show function composition.

These are the lessons I learned, translating some code examples to C#.

A simple solution to find if an array is special

Let’s find if an array is special.

An array is special if every pair of consecutive elements contains two numbers with different parity. For example, [4,3,1,6] isn’t special. It has three pairs: 4,3, 3,1, and 1,6. The second pair has two odd numbers, which makes our array “not special.”

Here’s the LeetCode problem if you want to try it yourself.

And here’s my solution before using any of the new concepts from the talk,

IEnumerable<(int First, int Second)> Pairs(int[] array)
{
    for (int i = 0; i < array.Length - 1; i++)
    {
        yield return (array[i], array[i + 1]);
    }
    // Or simply:
    // return array.Zip(array.Skip(1));
}

bool HasDifferentParity((int First, int Second) pair)
    => pair.First % 2 != pair.Second % 2;

bool IsSpecial(int[] array)
    => Pairs(array).All(HasDifferentParity);

IsSpecial([1, 2, 3, 4]) // true
IsSpecial([4, 3, 1, 6]) // false

It uses Pairs() to generate the pairs of consecutive elements, HasDifferentParity() to find if both numbers in a pair are even or odd, and All() from LINQ. Nothing fancy!

Apart from the “usual” composition, there are other ways of composing functions

Here are some of them, in pseudo-code:

def i(x) = x
def k(x, y) = x
def ki(x, y) = y

def s(f, g) = \x => f(x, g(x))
def b(f, g) = \x => f(g(x)) // <--

def c(f) = \x, y => f(y, x)
def w(f) = \x => f(x, x)

def d(f, g) = \x, y => f(x, g(y))
def b1(f, g) = \x, y => f(g(x, y))
def psi(f, g) = \x, y => f(g(x), g(y)) // <--
def phi(f, g, h) = \x => g(f(x), h(x))

Let’s take a close look at two of them.

b is the composition we all know. It passes the result of the second function to the first one. And psi passes two values to the second function and then calls the first one with both results.

Let’s find if an array is special again, but composing functions

Let’s revisit IsSpecial() but using “psi” this time,

IEnumerable<(int First, int Second)> Pairs(int[] array)
    => array.Zip(array.Skip(1));

bool IsEven(int x) => x % 2 == 0;
bool NotEqual(bool x, bool y) => x != y;

// A more general signature would be:
// Func<T1,T1,T3> Psi<T1, T2, T3>(Func<T2,T2,T3> f, Func<T1,T2> g)
// But to make things easier:
Func<int, int, bool> Psi(Func<bool, bool, bool> f, Func<int, bool> g)
//                   ^^^
    => (x, y) => f(g(x), g(y));

var hasDifferentParity = Psi(NotEqual, IsEven); // <--
bool IsSpecial(int[] array)
    => Pairs(array).All(pair => hasDifferentParity(pair.First, pair.Second));

// or
bool IsSpecial(int[] array)
    => Pairs(array).All(pair => Psi(NotEqual, IsEven)(pair.First, pair.Second));
    //                          ^^^

IsSpecial([1, 2, 3, 4]) // true
IsSpecial([1, 2, 4, 3]) // false

Instead of HasDifferentParity(), it uses Psi(NotEqual, IsEven).

And again, here’s the pseudo-code of psi: psi(f, g) = \x, y => f(g(x), g(y)).

Psi(NotEqual, IsEven) receives two integers, calls IsEven with each of them, and passes both results to NotEqual.

That’s a new way of composition.

Let’s do it the Haskell way using MapAdjacent

From Haskell official docs, MapAdjacent transforms every pair of consecutive elements.

Here’s IsSpecial() one more time, but using MapAdjacent(),

// Pairs, IsEven, NotEqual, and Psi are the same as before...

bool IsSpecial(int[] array)
    => array.Select(IsEven).MapAdjacent(NotEqual).And();

// or
bool IsSpecial(int[] array)
    => array.MapAdjacent(Psi(NotEqual, IsEven)).And();

IsSpecial([1, 2, 3, 4]) // true
IsSpecial([1, 2, 4, 3]) // false

It transforms our array into an array of booleans–if every number is even–with Select(), then finds if every pair of booleans is different with MapAdjacent() and lastly collapses all the results with And().

As an alternative, it uses MapAdjacent() with Psi(NotEqual, IsEven) to avoid using Select() first. This alternative is closer to the previous one with Pairs() and All().

Since we don’t have MapAdjancent() and And(), here are their C# versions,

public static class Enumerable
{
    public static IEnumerable<TResult> MapAdjacent<TSource, TResult>(
            this IEnumerable<TSource> source,
            Func<TSource, TSource, TResult> func)
        => source.Zip(source.Skip(1), (first, second) => func(first, second));

    public static bool And(this IEnumerable<bool> source)
        => source.All(x => x);
}

MapAdjacent() is a wrapper of Zip() and And() is a wrapper of All().

This is one of the talks to replay multiple times if you want to grasp its concepts, since they’re not that common. Also, it covers the same example in other less known languages that I don’t even remember their names.

Since you won’t use those obscure languages, learn the composition part of the talk. It’s mind blowing. In any case, if you want to start with Functional Programming, you’ll need simpler concepts.

Using Lambda Expressions Doesn't Make Your C# Code Functional

C# will never become a truly functional language.

Sure, C# is borrowing features from functional languages like records, pattern matching, and switch expressions. But it doesn’t make it a functional language.

At its core, C# is an Object Oriented language — with mutable state baked in. But it doesn’t mean we can make it functional with a few tweaks.

That’s the main takeaway from the talk “Functional Programming on .NET” by Isaac Abraham at NDC Conference 2024.

Here are some other lessons I learned from that talk.

Here are two misconceptions about Functional Programming

1. “It’s difficult”

When we hear “Functional Programming” what comes to mind is obscure jargon like monad, monoids, and endofunctors. They sound like you have to grow a beard to understand them. That scares programmers away from Functional Programming.

2. “I’m already using lambda expressions”

Lambda expressions and other recent features are syntactic sugar to make C# less verbose. Lambda expressions simplify delegates. And we’ve had delegates since C# 1.0 when C# looked way less functional than today.

The truth is functional programming could be simpler and lambda expressions don’t make C# functional.

If lambda expressions and other features don’t make C# functional, what is it then?

Expressions and Immutability.

Expressions mean everything has a result. Think of old-style switch/case statements vs switch expressions. We can assign the result of a switch expression to a variable, but not an old-style switch/case. If it can be assigned to the left of =, it’s an expression.

// This is not an expression
switch (cardBrand)
{
    case "Visa":
        cardType = CardType.Visa;
        break;

    case "Mastercard":
        cardType = CardType.MasterCard;
        break;

    default:
        cardType = CardType.Unknown;
        break;
}

// This is an expression
var cardType = cardBrand switch
{
    "Visa" => CardType.Visa,
    "MasterCard" => CardType.MasterCard,
    _ => CardType.Unknown
};

And immutability means that everything is set for life. Think of records, the init keyword, and private setters.

With these two concepts, to make C# functional, we should forget about classes and inheritance to go with static methods. We should forget constructor injection and go with regular method parameters. And we should separate data from behavior instead of keeping them side by side in a class that mutates state.

Following these two concepts, a dummy Calculator that looks like this

public class Calculator(ILogger _logger)
{
    private int Value { get; private set; }
    public void Add(int a)
    {
        _logger.Log($"Adding {a}");
        Value += a;
    }
}

will look like this instead

public static class FunctionalishCalculator
{
    public int Add(Action<string> logger, int value, int a)
    {
        logger($"Adding {a}");
        return value + a;
    }
}

And in F#, it will look like this

let add logger value a =
    logger $"Adding {a}"
    value + a

A few lines of code. In F#, functions don’t have to live in classes. The compiler infers parameters types. And we don’t need to use the return keyword.

The perfect example of expressions and immutability in practice is LINQ.

LINQ is functional.

Every LINQ method returns a new collection. We don’t have “void” LINQ methods. Expressions. And no LINQ methods change the input collection. Immutability.

That’s why LINQ is the best of all C# features—It’s functional.

Voilà! We don’t need fancy features like discriminated unions to adopt functional ideas in C#. Only those two fundamental concepts. Sure, new features help a lot!

Start by keeping IO away from your core, at the edges. And go with expressions and immutability.

14 Quick Career Lessons After 10+ Years (and Lots of Trial and Error) as a Software Engineer

Coding is the easy part of software engineering.

We don’t get an instruction manual to survive the corporate world and navigate the “people and interactions” side of software engineering.

After over 10 years, 3 jobs, 2 “we have to let you go,” and lost of trial and error, these are 14 lessons I’ve learned while navigating a career as a software engineer:

1. You Can Lose Your Job At Any Time

You can’t control it. A pandemic, a recession, or anything else. But you can control: your online presence, emergency fund, and side income.

2. Detach Your Sense Of Meaning From Your Work

Work gives value and meaning.

But you’re not your work. You’re the books you’ve read, places you’ve visited, and people you’ve met. You’re your connections, experiences, and knowledge.

You’re more than a job.

3. Build Multiple Sources Of Income

A rental property, stock portfolio, or digital products. You name it.

Don’t rely only on your salary and don’t live paycheck to paycheck. There’s no safe job. See #1.

4. Always Be Ready To Leave Your Current Job

If you wait for a layoff to take action, it’s already too late.

Grow your network, have your CV updated, and keep your “tell me about yourself” muscles in shape. Yes, hiring is broken.

5. Change Jobs Often

Or at least, don’t stay too long at stagnant jobs.

Otherwise, your CV will become outdated, your interviewing skills will get rusty, and you will leave money on the table.

6. Climbing The Corporate Ladder Is A Myth

Anyone can add or remove steps in the ladder. Or remove the entire ladder. The best ladder to climb is the one you build for yourself.

A new title comes with more meetings, extra hours, and the same salary. Instead of optimizing for a title, optimize for a lifestyle.


7. Vacations Don’t Change An Unfulfilling Job

Neither do pay raises.

They only move a “death sentence” a couple of months ahead.

8. You Don’t Have To Feel Miserable

If you don’t feel like getting out of bed to work at that place, make a change. Find a way to motivate yourself and keep learning. Or look for “greener pastures.”

9. You Don’t Get Burned Out By Doing Too Much

But by doing too little of the things you care about. (Jim Kwik, brain coach)

And seek help when people around you start to be affected by your burnout.


10. Software Projects Don’t Fail Because Of Programming Languages And Tech Stacks

Nope! They fail because of poor communication and unclear expectations. The same as marriages. Even unclear expectations are a communication issue.

11. Don’t Be A Hero. Be A Team Player

If you are the only one who knows or does something in your team, you’re being a hero.

It feels great when you are the one who saves the day, but being a hero is a trap. A hero can’t get sick, go on vacation, or be promoted. Be a team player instead.

12. The Minute You Learn Something, Teach It

That’s from Show Your Work by Austin Kleon.

13. Don’t Ask Someone Who’s Leaving To Finish An Important Task At Work

As soon as he leaves, something will happen to the task he finished: an unexpected issue, a change in requirements, or a new scenario nobody saw coming. And he will be the only one who knows how to handle it. Too late. He’s already gone.

14. The More Senior You Get, The Less It’s About Coding

There’s more than coding:

  • Team dynamics
  • Project management
  • Inter-team communication
  • Scope and deadline negotiations
  • Managing stakeholders’ expectations

There’s more than only source code in a text editor.

Parting Thought

Hoping and praying without taking action is a bad career strategy. Instead, define what you want from your career. Money? Connections? Recognition? Create a career plan that reflects that, pick the right jobs, and always have an escape plan.

“Your career is your responsibility, not your employer’s”Clean Coder by Uncle Bob.

Refactor your coding career with my free 7-day email course. In just 7 emails, you’ll get 10+ years of career lessons to avoid costly career mistakes.