Skip to content

Instantly share code, notes, and snippets.

@noelwelsh
Last active November 14, 2018 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save noelwelsh/179cf1192835c59cbdfa85872d11f114 to your computer and use it in GitHub Desktop.
Save noelwelsh/179cf1192835c59cbdfa85872d11f114 to your computer and use it in GitHub Desktop.
A simple questionnaire system
scalaVersion := "2.12.7"
final case class Id(value: String) extends AnyVal
sealed trait Question[A] {
def id: Id
def prompt: String
}
object Question {
final case class IntQuestion(id: Id, prompt: String) extends Question[Int]
final case class StringQuestion(id: Id, prompt: String) extends Question[String]
final case class MultipleChoiceQuestion(id: Id, prompt: String, choices: List[String]) extends Question[String]
def int(id: Id, prompt: String): Question[Int] =
IntQuestion(id, prompt)
def string(id: Id, prompt: String): Question[String] =
StringQuestion(id, prompt)
def multipleChoice(id: Id, prompt: String, choices: List[String]): Question[String] =
MultipleChoiceQuestion(id, prompt, choices)
}
sealed trait Questionnaire[A] {
import Questionnaire._
def :::[B](head: Question[B]): Questionnaire[(B, A)] =
CompoundQuestionnaire(head, this)
}
object Questionnaire {
final case object EmptyQuestionnaire extends Questionnaire[Unit]
final case class CompoundQuestionnaire[A,B](
head: Question[A] ,
tail: Questionnaire[B]
) extends Questionnaire[(A, B)]
val empty: Questionnaire[Unit] = EmptyQuestionnaire
}
sealed trait Answer[A] {
def id: Id
def value: A
}
object Answer {
final case class IntAnswer(id: Id, value: Int) extends Answer[Int]
final case class StringAnswer(id: Id, value: String) extends Answer[String]
def int(id: Id, value: Int): Answer[Int] =
IntAnswer(id, value)
def string(id: Id, value: String): Answer[String] =
StringAnswer(id, value)
}
sealed trait Answers[A] {
import Answers._
def :::[B](head: Answer[B]): Answers[(B, A)] =
CompoundAnswers(head, this)
}
object Answers {
final case object EmptyAnswers extends Answers[Unit]
final case class CompoundAnswers[A,B](
head: Answer[A],
tail: Answers[B]
) extends Answers[(A, B)]
val empty: Answers[Unit] = EmptyAnswers
}
trait Interpreter {
def apply[A](questionnaire: Questionnaire[A]): Answers[A]
}
object DummyInterpreter extends Interpreter {
def ask[A](question: Question[A]): Answer[A] = {
import Question._
question match {
case IntQuestion(id, _) => Answer.int(id, 42)
case StringQuestion(id, _) => Answer.string(id, "Yeah!")
case MultipleChoiceQuestion(id, _, c) => Answer.string(id, c.head)
}
}
def apply[A](questionnaire: Questionnaire[A]): Answers[A] = {
import Answers._
import Questionnaire._
questionnaire match {
case EmptyQuestionnaire => EmptyAnswers
case CompoundQuestionnaire(hd, tl) =>
ask(hd) ::: apply(tl)
}
}
}
object ConsoleInterpreter extends Interpreter {
def printPrompt(prompt: String, format: String): Unit = {
println(prompt)
print(s"$format >")
}
def ask[A](question: Question[A]): Answer[A] = {
import Question._
import scala.io.StdIn
question match {
case IntQuestion(id, p) =>
printPrompt(p, "int")
Answer.int(id, StdIn.readInt())
case StringQuestion(id, p) =>
printPrompt(p, "text")
Answer.string(id, StdIn.readLine())
case MultipleChoiceQuestion(id, p, c) =>
printPrompt(p ++ c.mkString("\n", "\n", ""), "multiple choice")
Answer.string(id, StdIn.readLine())
}
}
def apply[A](questionnaire: Questionnaire[A]): Answers[A] = {
import Answers._
import Questionnaire._
questionnaire match {
case EmptyQuestionnaire => EmptyAnswers
case CompoundQuestionnaire(hd, tl) =>
ask(hd) ::: apply(tl)
}
}
}
object Example {
val questionnaire =
Question.string(Id("Name"), "What is your name?") :::
Question.int(Id("Year"), "What year were you born?") :::
Question.multipleChoice(Id("Icecream"), "What is your favourite icecream flavour?", List("Vanilla", "Chocolate", "Pistachio", "Lemon")) :::
Questionnaire.empty
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment