🎓 A1Learner
🔍
0 of 27 lessons completed

R Grade Book Project

The Grade Book project joins the course tools in one connected program. An S4 class holds each grade, a generic describes it, a custom condition guards it, and functionals summarise the set.

The Grade class

setClass builds the Grade class with a character slot and a numeric slot. The assigned setValidity call adds the range rule, and the print shows the slot dump for one grade.

setClass("Grade", slots = c(student = "character", score = "numeric"))
v <- setValidity("Grade", function(object) {
  valid <- object@score >= 0 && object@score <= 100
  if (!valid) {
    return("score must be between 0 and 100")
  }
  return(TRUE)
})
ana <- new("Grade", student = "Ana", score = 88)
print(ana)
print(ana@score)

Describe a grade

The assigned setGeneric call builds the describe generic. The Grade method joins the student and the score into one line.

setClass("Grade", slots = c(student = "character", score = "numeric"))
v <- setValidity("Grade", function(object) {
  valid <- object@score >= 0 && object@score <= 100
  if (!valid) {
    return("score must be between 0 and 100")
  }
  return(TRUE)
})
g <- setGeneric("describe", function(object) {
  standardGeneric("describe")
})
setMethod("describe", "Grade", function(object) {
  paste0(object@student, " scored ", object@score)
})
ana <- new("Grade", student = "Ana", score = 88)
bo <- new("Grade", student = "Bo", score = 74)
print(describe(ana))
print(describe(bo))

Catch an impossible grade

make_grade checks the score before it builds the object. A score above 100 raises the custom condition, and the class handler turns it into a caught message.

setClass("Grade", slots = c(student = "character", score = "numeric"))
v <- setValidity("Grade", function(object) {
  valid <- object@score >= 0 && object@score <= 100
  if (!valid) {
    return("score must be between 0 and 100")
  }
  return(TRUE)
})
make_grade <- function(student, score) {
  if (score > 100) {
    stop(structure(list(message = "grade cannot be above 100", call = NULL), class = c("impossible_grade", "error", "condition")))
  }
  new("Grade", student = student, score = score)
}
result <- tryCatch(make_grade("Dee", 105), impossible_grade = function(e) {
  paste0("caught: ", conditionMessage(e))
})
print(result)
ok <- make_grade("Ana", 88)
print(ok@score)

Summarise several grades

Map pulls the score out of each grade as a list. Reduce adds the scores into one total, and Filter keeps the grades at 80 or above before Map reads their names.

setClass("Grade", slots = c(student = "character", score = "numeric"))
v <- setValidity("Grade", function(object) {
  valid <- object@score >= 0 && object@score <= 100
  if (!valid) {
    return("score must be between 0 and 100")
  }
  return(TRUE)
})
grades <- list(new("Grade", student = "Ana", score = 88), new("Grade", student = "Bo", score = 74), new("Grade", student = "Cy", score = 95))
scores <- Map(function(grade) {
  grade@score
}, grades)
print(scores)
print(Reduce("+", scores))
passing <- Filter(function(grade) {
  grade@score >= 80
}, grades)
print(Map(function(grade) {
  grade@student
}, passing))

Example

setClass("Grade", slots = c(student = "character", score = "numeric"))
v <- setValidity("Grade", function(object) {
  valid <- object@score >= 0 && object@score <= 100
  if (!valid) {
    return("score must be between 0 and 100")
  }
  return(TRUE)
})
g <- setGeneric("describe", function(object) {
  standardGeneric("describe")
})
setMethod("describe", "Grade", function(object) {
  paste0(object@student, " scored ", object@score)
})
make_grade <- function(student, score) {
  if (score > 100) {
    stop(structure(list(message = "grade cannot be above 100", call = NULL), class = c("impossible_grade", "error", "condition")))
  }
  new("Grade", student = student, score = score)
}
grades <- list(make_grade("Ana", 88), make_grade("Bo", 74), make_grade("Cy", 95))
print(Map(describe, grades))
bad <- tryCatch(make_grade("Dee", 105), impossible_grade = function(e) {
  paste0("caught: ", conditionMessage(e))
})
print(bad)
scores <- Map(function(grade) {
  grade@score
}, grades)
total <- Reduce("+", scores)
print(total)
print(total / length(grades))
passing <- Filter(function(grade) {
  grade@score >= 80
}, grades)
print(length(passing))

Key points

  • Grade is an S4 class with a student slot and a score slot.
  • The validity rule keeps every score between 0 and 100.
  • The setValidity and setGeneric results are assigned, so those calls print nothing.
  • describe is a generic with a Grade method that builds one line of text.
  • make_grade raises a custom impossible_grade condition for a score above 100.
  • tryCatch catches that condition by its class name and returns the message.
  • Map, Reduce and Filter summarise a list of grades.

In this example, the program builds three grades and describes each one with Map. The custom condition catches the grade of 105 by class, Reduce totals the scores to 257, and Filter keeps the two grades at 80 or above.

Try it Yourself

Edit the code, then press Run.

Running is simulated · rdrr.io R Snippets

Exercise

A grade of 105 reaches make_grade in the project. What happens?