-
Notifications
You must be signed in to change notification settings - Fork 1
Check
This module’s interface must include the functions checkSyntax and checkSemantics.
These are used to provide feedback on student submissions via OutputCapable.
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.
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.
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-
FilePathis 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. -
TaskDataandSubmissionare types defined inGlobal.
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).
💡
checkSemanticsonly runs ifcheckSyntaxdoesn’t abort.
You may add more constraints on m, just as explained in TaskData.
Here's the full list of options:
MonadDiagramsMonadGraphvizMonadCacheMonadLatexSvg-
MonadWriteFile(deprecated; try usingMonadCacheinstead)
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
Checkmodule 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.
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 scoreAuthor: patrick.ritzenfeld@uni-due.de