Spaced Repetition
Last year, I discovered Anki, which is software for managing digital flashcards. The problem it tries to tackle is that of memorizing. Anki makes memory a choice. This sounds too good to be accurate, but I've found it true in practice. There are plenty of resources that explain how to use spaced repetition and why it's useful, so I won't be getting into that. If you've never heard of the term, or if you haven't had the motivation, I suggest you at least try it for a few days.
Arguments as to why spaced repetition is great come from Cognitive Science and thus, anything that tries to explain why Anki is good, usually has to dive into some of the research on this field. Bonus tip: if you are a programmer, this article is super enlightening.
It's good to note that Anki is not only useful for remembering simple facts. You can understand complex subjects by using flashcards. A great quote by Michael Nielsen about this:
It's possible to develop virtuoso skill using Anki, a skill aimed at understanding complex material in depth, not just memorizing simple facts.
And he shows so in a very insightful article.
Anki Cards
A flashcard is a card that has a question on its front side and the answer on its back, like so:
Q: What is the world's population?
A: 8.1 billion as of January 31, 2024.
Simple enough, right? The interesting thing that makes flashcards work is how you manage their review cadence. Memory works in a way that makes recall speed and accuracy decline with time until you can no longer remember.
It turns out that spaced repetition counters this effect, so to remember something, you just need to review it once in a while, and every time you remember the fact, you'll recall it more efficiently for a longer period.
If that sounds like a mouthful, maybe this chart helps:
There are a few algorithms for the cadence of reviews, but the one I'm using is FSRS, which you can use with Anki.
Evolving a Card
The Problem
The hardest part when I started using Anki was figuring out how to actually make the cards. Even after reading all the links posted throughout this article, I still had many questions and had zero confidence in creating good flashcards. I even went and read rules for formulating knowledge, which is a guide to creating good learning material. An excerpt from the article:
The speed of learning will depend on the way you formulate the material. The same material can be learned many times faster if well formulated! The difference in speed can be stunning!
I immediately made use of my shiny new Anki app and ankified (created flashcards for) said rules. However, this proved not to be helpful. Even if I memorized them, I wasn't learning them. So, only after two or three months of practice have I found a few rules that cater to my style and that I'll share in a second.
So, if you find yourself in this situation, my advice is to just create the cards without trying to be perfect, and you'll develop a sense of what works for you and what doesn't pretty quickly. Just trust the process. Using Anki is a skill in itself, and so it can be learned with patience.
An Example
"Okay, that's all good, but I wanna know how you do it". Alright, let's start with a simple card. The topic is Rust's standard library, so you might not be familiar with it but don't focus on the content; focus on the structure of the cards.
What follows was the first card on the
Rust std
Q: What is a `Cursor<T>`?
A: A `Cursor` wraps an in-memory buffer and provides it
with a `Seek` implementation.
Cursors are used with in-memory buffers, anything implementing
`AsRef<[u8]>`, to allow them to implement `Read` and/or `Write`,
allowing these buffers to be used anywhere you might use a
reader or writer that does actual I/O.
The standard library implements some I/O traits on various
types which are commonly used as a buffer,
like `Cursor<Vec<u8>>` and `Cursor<&[u8]>`.
You can immediately tell I am not learning all of that at once. It took me a few reviews to realize so, even though I did remember a lot, it was fuzzy and unclear.
An easy improvement is to split the card. How big should they be? As short as possible. When recalling, you'll make sense of it anyway, so the idea is to keep it as terse as you can while keeping semantics intact (though I've found it's okay to keep extra words). I split the above into:
Q: What is a `Cursor<T>`?
A: `Cursor` wraps an in-memory buffer to give it a `Seek` implementation.
---
Q: What is an in-memory buffer?
A: `AsRef<[u8]>`
---
Q: Why use a `Cursor<T>`?
A: Implement `Read` and/or `Write`.
---
Q: What types are commonly used as buffers?
A: `Cursor<Vec<u8>>` and `Cursor<&[u8]>`.
It might seem unintuitive to think that it is easier to memorize four cards than just one. However, our recall will be much easier now since the context we need to remember per question is much less. It also means that these are "chainable", as thoughts are: you first think about what a Cursor is, which gives leeway into what you use it for, and so on.
These cards are in better shape now, but there are gaps that we need to fill. For example, if you don't know what
Seek
There is another substantial improvement. If we only ever recall the front-to-back direction of cards that contain equivalence relations, we will indeed remember the front-to-back relationship, but it will be hard to recall the back-to-front one. For example, if we only practice the
in-memory buffer -> AsRef<[u8]>
AsRef<[u8]>
in-memory buffer
mesa
table
The table is over there
table
So, with all of these in mind, the second flashcard above would result in:
Q: Rust: What is an in-memory buffer?
A: `AsRef<[u8]>`
---
Q: Rust: `AsRef<[u8]>`
A: What is an in-memory buffer?
Interference
One final thing that is hard to avoid is memory interference. In the example above, you might find that it's hard to remember which of these two answers is right:
Q: Why use a `Cursor<T>`?
---
A: `Cursor` wraps an in-memory buffer to give it a `Seek` implementation.
A: Implement `Read` and/or `Write`.
This happens because both cards are quite similar. You can answer both "What is a
Cursor<T>
Cursor<T>
Cursor
Q: Why would you need a `Cursor<T>`?
A: Sometimes you must implement `Read` and/or `Write`.
Conclusion
Hopefully, this article helps you get faster to a point where you can confidently create flashcards, avoiding some of the pitfalls I've already been through. If not, that's okay anyway; you'll figure it out with time.
See you in the next one!