I have been assisting my first series of classes in computer science in the past months. One of my course is an introduction to computer programming and algorithmics, addressed to BAC+3 non-CS students with a previous exposure to these concepts that ranges from none to limited. The support language that was chosen is Visual Basic for Applications (VBA), the macro language bundled with Microsoft Excel.
This language is quirky and not very elegant, to say the least. It was chosen with the rationale that it might be the only language that they would be able to run by default on their work computer in the future.
The course has a very straightforward structure but does not feature much content that could serve to prepare their exam (which is a TP noté). I had been looking into the efficiency of self-assessment before and I thought it could be worth a shot to implement a generic quiz framework in my website to provide students with a simple web page they could use to self-assess their knowledge of the course, prior to the exam.
Structure of the framework
I wanted something that did not require the client to send any request during the assessment, as this would break the static design of my website, and it would imply that I could learn the result of their evaluation, which I wanted to avoid.
Thus I wrote a tiny JavaScript code with a simple state-machine that tracks the current question and which questions have been failed. I tried to keep the code as simple as possible, leveraging the <form method="dialog"> tag to prevent the use of user-defined events. The script fetches questions from a JSON file which provides information on the question: its kind (open, multiple-choice, …), its answer, its followup text, etc.
From the point of view of Hakyll, the Haskell framework of my static site generator (SSG), it would be as simple as integrating a base JSON element inside a “quiz” template. However this would mean that I’d have to write the list of questions in JSON format directly (1) and, more annoyingly, the content of the questions in HTML (2). This would make the question write-up workflow quite unwieldy.
Extending the SSG
Pandoc is a dependency of Hakyll and is thus readily available from within the code of my SSG, just like the Aeson JSON-parsing package. To address (2), I wrote a simple extension to my SSG that features (among others) the following functions:
transformMarkup :: (MonadFail m) => (Markup -> m Text) -> ByteString -> m ByteString
renderMarkup :: Markup -> Compiler Text
questionCompiler :: Compiler (Item String)The transformMarkup function decodes the given byte string as a datatype which represents questions, applies the given (monadic) transformation from markup text (either markdown or Typst) to HTML to all the relevant nodes; and encodes it back as JSON (in the format understood by the client quiz script).
The renderMarkup function is the one that transforms markup text to HTML by using Pandoc’s PandocPure :: PandocMonad. Its most general type is also (MonadFail m) => Markup -> m Text (it only uses return and fail to forward the errors raised by Pandoc), and is specialised to Hakyll’s Compiler monad only for readability and ease of composability.
Finally, the questionCompiler takes care of generating the JSON, handles UTF-8 operations and composes the previous functions to provide the final JSON.
Typst as a JSON generator
Writing questions in Typst has several advantages over markdown, notably for describing math equations. Additionally, its main LSP provides a live-editing preview, which is particularly useful for write-up.
In order to address (1), the core observation is that Typst also features a query system. A call on the command-line to e.g.
typst query my-document.typ '<my-label>' --field valuereturns a JSON list of all objects labeled by my-label in the document. Moreover, the metadata operator allows to introduce arbitrary query-extractable objects that will not be rendered in the document.
I wrote a simple Typst package called Assess which provides a way to declare questions and automatically makes them accessible to the query system. It also provides templates to render the questions directly in PDF format (for instance in the like of a set of physical flashcards). This extra rendering is easy to integrate in Hakyll.
Conclusion
For this source file, the end result is this web page, and that PDF file (all pages are in French).
My curriculum is not oriented towards web development at all but my time tinkering with JavaScript ended up being surprisingly enjoyable. I found Mozilla documentation to be particularly valuable and helped me get more at ease with all three of JS, HTML and CSS.
On the other hand, I was pleased to see how easy it was to introduce this (simple) extension to my SSG. I find Haskell code conceptually very clear. Unfortunately I struggled a bit to get the Haskell LSP working properly with several directories. In the end it worked fine but this experience was quite frustrating. Likewise, browsing the vast amount of documentation of several packages and finding the correct functions to deal with some non base types like ByteString without importing useless dependencies was more of a challenge than I thought it would be, even with the use of tools like Hoogle.
The addition of this feature on my website after some inactivity was also an occasion to make updates to other parts of my blog, as well as to the Nix-based build & development environment.
Time will tell, but I hope this framework will prove to be as useful and as reusable as I intend it to be.