This package provides everything necessary to create spaced repetition software using the SM-2 algorithm. The SM-2 algorithm was one of the earliest computerized implementations of a spaced repetition algorithm (created in 1988 by Piotr Wozniak) and has been released for free public use when accompanied by the following notice:
Algorithm SM-2, (C) Copyright SuperMemo World, 1991.
For details about this algorithm, please refer to the following description, written by its creator.
This package differs from the reference implementation of the SM-2 algorithm by sorting due items in decreasing severity of being due (i.e. more overdue items will be presented first).
The building blocks of this package are Cards and Decks. In simple terms, a Card may be thought of as a single flashcard and a Deck as a list or collection of Cards.
A Deck represents a list of cards to be studied (this might be called a "collection" in other software). It is simply an Array of Card and requires no special creation or manipulation. Maintaining the state of a Deck may be handled by the user of the module or by this module itself. In general, it is probably best not to add a massive quantity of new (unstudied) cards to a deck at once.
A Card may be created by use of the newSRSData function, as in the following example:
type alias MyFlashcard =
Card { prompt : String, answer : String }
myFlashcard : MyFlashcard
myFlashcard =
{ prompt = "SYN"
, answer = "SYN-ACK"
, srsData = newSRSData
}SRSData contains all data necessary for the SM-2 scheduling algorithm and may be created with the newSRSData function. It may additionally be saved/loaded using the Json encoder/decoder in this package
newSRSData creates a new SRSData for inclusion in a Card.
Since Card data must necessarily be preserved between sessions, a Json encoder/decoder is provided for SRSData. It may be utilized as follows:
import Json.Decode as Decode
import Json.Encode as Encode
type alias MyFlashcard =
Card { prompt : String, answer : String }
myFlashcardConstructor : SRSData -> String -> String -> MyFlashcard
myFlashcardConstructor srsData prompt answer =
{ prompt = prompt
, answer = answer
, srsData = srsData
}
myFlashcardToJson : MyFlashcard -> String
myFlashcardToJson myCard =
Encode.encode 0 <|
Encode.object
[ ( "srsData", encoderSRSData myCard.srsData )
, ( "prompt", Encode.string myCard.prompt )
, ( "answer", Encode.string myCard.answer )
]
myFlashcardDecoder : Decode.Decoder MyFlashcard
myFlashcardDecoder =
Decode.map3 myFlashcardConstructor
(Decode.field "srsData" decoderSRSData)
(Decode.field "prompt" Decode.string)
(Decode.field "answer" Decode.string)
jsonToMyFlashcard : String -> Result Decode.Error MyFlashcard
jsonToMyFlashcard str =
Decode.decodeString myFlashcardDecoder strencoderSRSData provides a Json encoder for encoding SRSData from a Card.
decoderSRSData provides a Json decoder for decoding SRSData for a Card.
The SM-2 algorithm depends on grading answers on a scale from 0-5, with 3 incorrect and 3 correct responses. For some use cases, it may be able to programmatically determine the Answer from a user's response. In other cases, however, the user may need to self-report.
The Answer type represents how accurate/certain a user's response was to a card and must be passed to answerCard whenever a Card is reviewed. This package strives to provide type names that are generally sensible/understandable, but the slightly more explanatory descriptions provided by the creator of the algorithm are presented below:
Perfect -- "perfect response"
CorrectWithHesitation -- "correct response after a hesitation"
CorrectWithDifficulty -- "correct response recalled with serious difficulty"
IncorrectButRemembered -- "incorrect response; where the correct one seemed easy to recall"
IncorrectButFamiliar -- "incorrect response; the correct one remembered"
NoRecollection -- "complete blackout"
answerCardInDeck functions analogously to answerCard but handles maintenance of the Deck, which is typically what one would desire. When a card is presented to the user and answered, answerCardInDeck should be called with the current time (in the Time.Posix format returned by the now task of the core Time module), an Answer, the index of the card in the Deck, and the Deck itself. It returns the updated Deck. Use this function if you simply want to store a Deck and not worry about updating it manually (which is most likely what you want). Otherwise, use answerCard to handle updating the Deck manually. Handling the presentation of a card is the responsibility of the implementing program, as various behaviors might be desirable in different cases. Note that if an invalid (out of bounds) index is passed, the Deck is returned unaltered.
When a card is presented to the user and answered, answerCard should be called with the current time (in the Time.Posix format returned by the now task of the core Time module) and an Answer. It returns the updated card, which should replace the card in the Deck. Use this function if you want to handle updating the Deck manually; otherwise, use answerCardInDeck. Handling the presentation of a card is the responsibility of the implementing program, as various behaviors might be desirable in different cases.
Besides answering cards, this package handles determining which cards in a Deck are due and require study.
getDueCardIndices takes the current time (in the Time.Posix format returned by the now task of the core Time module) and a Deck and returns the indices of the subset of the Deck that is due for review (as List Int). While the SM-2 algorithm does not specify this, the returned indices will be sorted in the following order:
getDueCardIndices assumes that a new day begins after 12 hours, e.g. if a card is scheduled to be studied the next day, it will become due after 12 hours of elapsed time. This can of course create edge cases where cards are reviewed too "early" if one studies very early in the morning and again late at night. Still, only very "new" cards would be affected, in which case the adverse effect is presumably minimal.
getDueCardIndicesWithDetails takes the current time (in the Time.Posix format returned by the now task of the core Time module) and a Deck and returns the subset of the Deck that is due for review (as a list of records), providing their index and which queue they are currently in, with any relevant queue details. While the SM-2 algorithm does not specify this, the returned indices will be sorted in the following order:
getDueCardIndicesWithDetails assumes that a new day begins after 12 hours, e.g. if a card is scheduled to be studied the next day, it will become due after 12 hours of elapsed time. This can of course create edge cases where cards are reviewed too "early" if one studies very early in the morning and again late at night. Still, only very "new" cards would be affected, in which case the adverse effect is presumably minimal.
If you require specific details for a single card, you may use the provided functionality here. If you need details for all due cards, just use getDueCardIndicesWithDetails.
QueueDetails represents the current status of a card.
NewCard -- A card that has never before been studied (encountered) by the user.ReviewQueue {...} -- A card that is being reviewed for retention.lastSeen : Time.Posix -- The date and time the card was last reviewed.intervalInDays : Int -- The interval, in days from the date last seen, that the card was slated for review in.RepeatingQueue {...} -- A card to which an unsatisfactory answer was given, slated for review before the end of the session.intervalInDays : Int -- The interval, in days from the date last seen, that the card was slated for review in. This will reset to the based interval (1 day) if the card was answered incorrectly.getCardDetails returns the current queue status for a given card. If you require this for every due card, simply use getDueCardIndicesWithDetails.
A
Cardrepresents a single question or unit of knowledge the user will review. The algorithm specifies that knowledge should be split into "smallest possible items," with each of these being aCard; in general terms, each would represent a single flashcard.Cardis defined as an extensible record; as such, whatever necessary custom fields for a use case may simply be included in the record, e.g.:A
Cardcontains only the information necessary for scheduling and nothing else; all other information should be added as in the above example.