Learning to Learn

Rex Ng
7 min readOct 15, 2022

Let’s be honest, being in tech is tougher than what people might think. I have had friends thinking my work day consists of typing two lines of code and browsing Reddit all day long. In fact, competition is always fierce and keeping yourself up-to-date with new technologies is not easy at all. Take my functional programming learning journey for an example, I have been doing it for more than seven years and to this date, I still cannot confidently say I am good at it.

But since I am gathering my thoughts on an evening, using my own experience of learning Haskell as an anecdote, I thought it would be useful to share my learning methodology and journey.

I hope this can serve as an inspiration to those struggling with whatever they are trying to learn.

Four Stages of Competence

Four Stages of Competence

I was shown this model and I like how it formalises learning and competency a lot. I do think it reflects how I learn most things.

Haskell is known to be an intimating programming language. It is not like any mainstream languages I am used to. It is functional, pure, and non-strict, while most languages are object-oriented, impure, and eager. It took me years to wrap my head around the simplest syntax and its computation model.

My first year of actually dedicating time to learning Haskell was very nerve-wracking. I had a C♯ day job and little experience with F♯. At that time I could not understand why on earth would people want to program in Haskell. Now I realised that is because I was at the stage of Unconscious Incompetence.

Looking back, if I had an opportunity to give some advice to my past self, I would tell him to just absorb whatever information he found or was told and don’t try to question it. Questioning the authenticity of information at this stage is unproductive because you are still at a ‘you-don’t-know-what-you-don’t-know’ phase. Questions like ‘How do I do dependency injection in Haskell’ and ‘Why there are no loops in Haskell’ are not helpful to learning because Haskell simply has a different way of achieving the same goals.

If you are lucky enough to have an instructor or a mentor on the subject you are learning, please, don’t try to pretend to be smarter than them and question why something has to be done a certain way. At the same time if you are teaching beginners Haskell, don’t try to answer the questions above with ‘You should use free monad instead of dependency injection’ or ‘Loops are just catamorphisms in disguise’. These answers, albeit well-intentioned, are actively unhelpful to learners at this stage. Beginners are simply not capable of learning at such an abstract level without understanding the basics. Instead, I would advise instructors to simply avoid answering these questions for the time being.

Yes, as learners, you are going to be stuck at figuring out type signatures of functions and perhaps simple list manipulation exercises. These are boring and menial no doubt. But this helps with muscle memory, not dissimilar to how people gain muscles by performing repetitive motions in a gym. It is only through repetitions you will start to understand and move on to the next stage.

Am I doing the right thing?

If you were able to tough it out in the first stage, congratulations! You have reached Conscious Incompetence. This is the stage where I would recommend making lots of observations in the form of mental notes.

At this stage, you should have had the basics down. If you are learning Haskell, I would expect you to already know how to implement a reverseList function, and perhaps how to use the Maybe data type to represent the absence of value.

You should still try to absorb as much information as possible, but a learner should start making observations. When I was learning Haskell I made a lot of observations when trying to compare it with C♯:

  • Lists are usually pattern-matched to x:xs, the use of x and xs as binding names are prevalent in Haskell codebases
  • where (post-declaration) is usually favoured to declare values instead of let (pre-declaration)
  • If a function takes another function as a parameter (i.e. a higher-order function), the parameter is usually named f, if there are two function parameters, they are usually named f and g
  • Type annotations for top-level bindings are usually favoured, even though the compiler can infer them

Even things like how many line breaks are used between function definitions, line lengths of code files, and indentation styles should also be noteworthy to students at this stage.

You might not necessarily have good explanations for these observations at the moment, and that is completely OK. These observations are not necessarily knowledge that pertains to the skill you are learning, but they are rather the ‘vibe’, the ‘culture’, the ‘idiom’ we should be aware of, because they let us ‘fit in’.

When in Rome, do as the Romans do.

Learning a new skill is not just about learning the hard facts. It is also about participating in its culture and understanding there are simply different ways to achieve the same goal.

How do I do X?

After a certain amount of repetitive training and making observations, you should reach a point where you are finally ready to ask deep questions like ‘how/why do I do X?’. At this stage, you should be reasonably confident about what you do and do not know, because you have reached Conscious Competence.

Being quite self-aware, I would describe myself as being at this stage in my Haskell learning journey. Because I usually have a pretty good idea of what I do not know. Generalised Abstract Data Types (GADTs) completely baffle me, Free Monads sound like alien language to me, and Arrows? Yeah, I only know those you shoot with a bow.

Take arrows for an example:

I am at a level where I can comfortably and confidently learn something new without confusing it with the existing knowledge I have retained because I have gained a firm grasp of the basics. And because I know what identity functions are and when to use them, I know I am ready to move up the abstraction ladder to learn the identity morphism in categories, a generalisation of functions.

The difference between the present me and the seven-years-ago-beginner me is that I know exactly why I need to learn these abstract concepts, because I am competent with the basics and can tie abstract concepts to concrete engineering problems I have been facing.

At this stage, I found it is best to revisit the basics, and think hard about how you did reverseList in the previous stage, can it be refactored to a tail recursion? Perhaps using a fold instead? Which fold to use? What are the differences between foldr, foldl, and foldl'? How does it impact correctness and performance? These are considerations you would not have thought of during the previous stage because you were too busy focusing on correctness. Now that we have advanced, we should work on considering these orthogonal factors as well.

Trust your intuition

I do not have many skills that have reached this final level of competency: Unconscious Competence. Haskell is definitely not one of them. However, I do think my C♯ skills are.

They were multiple instances I would sense something was off with the C♯ code that I was asked to review but I couldn’t pinpoint exactly what was at the heat of the moment. When I slept on the problem, I could tell what exactly was wrong with the code. This kind of intuition is a sign that you have truly reached mastery of a subject. Your subconscious brain can make a connection so fast your conscious mind is not able to keep up with it.

But like a lot of shy introverts, I have a lot of self-doubts. I tend not to brag about my C♯ skills because I know humans have confirmation bias and can never be always right. There will always be someone who knows much more than me. When I do get an intuition, these self doubts always make me less confident in speaking up or raising flags.

Though over time I have learnt to trust those intuitions more than my negative self-talk. I have been using C♯ almost daily since ten years ago and I was given enough exposure to evaluate code from many angles: correctness, maintainability, and performance. These self-doubts are very difficult to get rid of, but I am trying.

My final piece of advice for readers possessing skills at this level is to learn how to verbalise your reasoning and intuition. If you cannot convince or persuade others your stance is superior, you have not reached mastery yet. Because I consider a true master to be one who possesses the skill themselves and is able to effectively communicate the idea to people on different skill levels. There is this series from Wired I like a lot. See how the expert can convey an idea to a child, all the way up to another expert? That to me is proof they have reached the pinnacle of mastery.

Shuhari (しゅはり) — To obey, to break away, to transcend

I think the four levels of competence model draws a lot of similarities to Shuhari, the Japanese martial arts philosophy where a learner first obeys, then breaks away, and finally transcends.

Notice both methodologies emphasise obeying as the very first step to learning something new. It is not that students should not be allowed to ask questions, the point is questions have their time and place. When students are willing to power through the initial struggle of overcoming the steep learning curve, the learning experience will become more enjoyable and the knowledge is going to be retained much better.

--

--