3

Motivation: I want to write an interface that uses questions from the R package exams in learnr questions/quizzes. In R/exams each question is either an R/Markdown (Rmd) or R/LaTeX (Rnw) file with a certain structure specifying question, solution, and further meta-information. The questions can contain R code to make them dynamic, e.g., sampling numbers or certain text building blocks etc. Hence, the workflow is that first the questions are run through knitr::knit or utils::Sweave and then embedded in a suitable output format.

Problem: When I rmarkdown::run("learnr+rexams.Rmd") a learnr tutorial that dynamically produces a question or quiz from an Rmd exercise I get the error:

Error in if (grepl(not_valid_char_regex, label)) { : argument is of length zero

The code for a simple reproducible example learnr+rexams.Rmd is included below. The reason for the error appears to be that learnr runs a function verify_tutorial_chunk_label() that tries to assure the the learnr R chunk labels are well formatted. However, confusion is caused by the chunks that are run by the R/exams package, unnecessarily leading to the error above.

Workarounds: I can disable the verify_tutorial_chunk_label() in the learnr namespace and then everything works well. Or I can use Rnw instead of Rmd exercises and then learnr does not conflict with Sweave(). Also, when I run my code outside of a learnr tutorial it works fine.

Question: Can I do anything less invasive to make exams cooperate with learnr? For example, setting some appropriate knitr options or something like that?

Example: This is the source for the minimal learnr tutorial learnr+rexams.Rmd that replicates the problem. Note that everything is very much simplified and only works for certain R/exams exercises, here using the function exercise template that ships with R/exams.

---
title: "learnr & R/exams"
output: learnr::tutorial
runtime: shiny_prerendered
---

```{r exams2learnr, include = FALSE}
exams2learnr <- function(file) {
  x <- exams::xexams(file)[[1]][[1]]
  x <- list(text = x$question, type = "learnr_text",
    learnr::answer(x$metainfo$solution, correct = TRUE))
  do.call(learnr::question, x)
}
## assignInNamespace("verify_tutorial_chunk_label", function() return(), ns = "learnr")
```

```{r rfunctions, echo = FALSE, message = FALSE}
exams2learnr("function.Rmd")
```

Running this tutorial (as noted above) replicates the error. To avoid it I can either uncomment the assignInNamespace() call or alternatively replace "function.Rmd" by "function.Rnw".

Achim Zeileis
  • 15,710
  • 1
  • 39
  • 49

1 Answers1

1

The problem is that by the time learnr::question() is called, knitr is no longer able to find the chunk label for the chunk where exams2learnr() was called. You can get around this by setting the current chunk label before calling do.call(learnr_question, x):

exams2learnr <- function(file, label = knitr::opts_current$get("label")) {
  force(label)
  x <- exams::xexams(file)[[1]][[1]]
  x <- list(
    text = x$question, 
    type = "learnr_text",
    learnr::answer(x$metainfo$solution, correct = TRUE)
  )
  knitr::opts_current$set(label = label)
  do.call(learnr::question, x)
}

This also lets you set the label dynamically if you want, which becomes the ID of the question in learnr.

grrrck
  • 1,066
  • 6
  • 7
  • This is very helpful, thanks! I had tried to propagate the knitr label but had tried only variants that did not work (before calling learnr::quiz but not each learnr::question). Now I understand why. Also the pointer that label= could be an argument is good, I have incorporated that into my code now. – Achim Zeileis Apr 15 '21 at 09:31
  • 1
    Thanks to this answer I could now finish the first draft of the `exams2learnr` package, including the full function `exams2learnr()`. It is not yet on CRAN but can be easily installed from R-Forge: `install.packages("exams2learnr", repos = "https://R-Forge.R-project.org")`. See also: – Achim Zeileis Apr 16 '21 at 06:09