Skip to content
patritzenfeld edited this page Feb 10, 2026 · 10 revisions

This module’s interface must include the functions checkSyntax and checkSemantics. These are used to provide feedback on student submissions via OutputCapable.

Module Format

The Check module generated by TaskData is simply a String constant. You’ll need to include it in the Gen value returned by getTask. Beyond that, you’re free to structure it however you like.

A common approach is to define it as a constant or function inside TaskData. You can then call it from getTask to include it in the generated value. We strongly recommend using quasi quotation here, as described in Task Structure.

Escaping Certain Symbols

When working with quasi quotation, there are two symbols you’ll need to escape:

Symbol Meaning Code Use Case
Backslash (\) Escape Character Lambda Functions
Hash (#) Interpolation Operator Pragmas (Extensions, GHC Options)

If you forget, you’ll run into a confusing error: the entire quasi-quoted block is displayed, but without any useful hint about what failed or where. It’s the kind of thing that can really trip you up.

Feedback

Your Check module needs to define the following functions:

checkSyntax :: OutputCapable m => FilePath -> TaskData -> Submission -> LangM m

checkSemantics :: OutputCapable m => FilePath -> TaskData -> Submission -> Rated m
  • FilePath is a type alias for String. It indicates where images and other files are saved. This is set by Autotool when it runs the function. You can safely ignore it if your task doesn’t make use of files.
  • TaskData and Submission are types defined in Global.

The checkSyntax function runs immediately after submission, but only shows output if it aborts. Otherwise, it completes silently, and any output is shown only after the deadline. This makes it useful for catching early issues before students miss the chance to fix them.

The checkSemantics function provides the final feedback. It’s shown either after the deadline or right away, depending on how the task is configured in Autotool. It also returns a score, which may contribute to the final grade (again, depending on the settings).

💡 checkSemantics only runs if checkSyntax doesn’t abort.

Available capabilities

You may add more constraints on m, just as explained in TaskData. Here's the full list of options:

  • MonadDiagrams
  • MonadGraphviz
  • MonadCache
  • MonadLatexSvg
  • MonadWriteFile (deprecated; try using MonadCache instead)

What to Interpolate?

With quasi quotation, you can inject values into the Check module before it’s written to disk. As a general rule, this is helpful in three situations:

Scenario Reason Applicable When
Constant from TaskSettings Use the value directly rather than referencing the definition Always
Function call on something concrete (not depending on TaskData) Compute it once and reuse file across all students If everything needed is already inside TaskData or easy to move there
Function call involving TaskData or values depending on it Pre-compute to reduce wait time for feedback If the computation is heavy or slow

💡 In Scenario 3, each student ends up with their own version of the Check module in the cache directory (assuming the task instance is unique). That’s not a problem in itself, but it does mean the file can’t be shared between students.

Interpolation Examples

Here’s a quick example for each of the three scenarios outlined above, along with how to escape special symbols. First, check defined inside the TaskData module:

{-# language QuasiQuotes #-}

module TaskData where


import String.Interpolate (i)
import TaskSettings (someTaskSetting)

.
.
.

getTask :: MonadRandom m => m (TaskData, String, Rendered Widget)
getTask = do
  ...
  pure (tData, check theValue, form)


sameForEveryStudent :: [Int]
sameForEveryStudent = ...


check :: SomeType -> String
check tDataDependent = [i|

{-\# language ApplicativeDo \#-} -- escape hash symbols

module Check where


mySharedValue :: [Int]
mySharedValue = #{sameForEveryStudent} -- Own definition if used multiple times (Scenario 2)


checkSyntax :: OutputCapable m => FilePath -> TaskData -> Submission -> LangM m
checkSyntax _ solution submission = do
  assertion #{myExpensivePredicate tDataDependent} $ text "precomputed and inserted (Scenario 3)"
  assertion $ doSomething mySharedValue submission
  pure ()

checkSemantics :: OutputCapable m => FilePath -> TaskData -> Submission -> Rated m
checkSemantics path solution submission = do
  when #{someTaskSetting} someExtraCheck -- configurable check (Scenario 1)
  let score = calcScore (map (\\x -> sqrt $ x^2 + 1) mySharedValue) submission -- escape backslashes
  pure score
|]

And this is how it looks once written to file (with concrete values chosen arbitrarily):

{-# language ApplicativeDo #-} -- escape hash symbols

module Check where


mySharedValue :: [Int]
mySharedValue = [1,-876,3,45691,...] -- Own definition if used multiple times (Scenario 2)


checkSyntax :: OutputCapable m => FilePath -> TaskData -> Submission -> LangM m
checkSyntax _ solution submission = do
  assertion False $ text "precomputed and inserted (Scenario 3)"
  assertion $ doSomething mySharedValue submission
  pure ()

checkSemantics :: OutputCapable m => FilePath -> TaskData -> Submission -> Rated m
checkSemantics path solution submission = do
  when True someExtraCheck -- configurable check (Scenario 1)
  let score = calcScore (map (\x -> sqrt $ x^2 + 1) mySharedValue) submission -- escape backslashes
  pure score

← Previous: TaskData | Next: Description →

Clone this wiki locally