Handling nested logic/switch statements


#1

Ayoo,

Since learning functional programming I’ve been stumped about how to properly handle nested logic cases. Take the following example: I’m coding a quiz. After the user clicks a possible answer, I update the elements to add classes indicating correctness:

// While looping over possible answers

let className = "question__answer " ++ (switch (selectedAnswer) {
  // If no answer has been selected yet, we don't need to add extra classes
    | None => ""
    | Some(selectedAnswerValue) => {
      // If this is the correct answer, add the corresponding class
      if (answer.correct) {
        "question__answer_correct"
      } else {
        // Only add the "failure" class if this was the selected answer
        if (selectedAnswerValue.text == answer.text) {
          "question__answer_incorrect"
        } else {
          ""
        }
      }
    }
  })

I know I’m doing it wrong. This is anything but concise and clear code and the compiler says there’s a syntax error. What is the alternative?

Thanks!


#2

The syntax error is from the end-of-line comments, which isn’t a thing in Reason. The only kind of comment in Reason is the block kind: /* comment */.

Other than that, I don’t think this looks all that terrible given the shape of the data. One minor improvement you could do is use else if instead of a nested if ... else block:

let className = "question__answer " ++ (switch (selectedAnswer) {
    | None => ""
    | Some(selectedAnswerValue) => {
      if (answer.correct) {
        "question__answer_correct"
      } else if (selectedAnswerValue.text == answer.text) {
        "question__answer_incorrect"
      } else {
        ""
      }
    }
  })

And going a bit further, you can skip the if-expression entirely and use guards instead:

let className = "question__answer " ++ (switch (selectedAnswer) {
    | Some(selectedAnswerValue) when answer.correct =>
        "question__answer_correct"
    | Some(selectedAnswerValue) when selectedAnswerValue.text == answer.text =>
        "question__answer_incorrect"
    | _ =>
        ""
  })

#3

I added the comments in after the fact, the compiler errors without them.


#4

It compiled fine for me just without the comments. Perhaps there’s something else that errors?

Edit: By fine I mean without syntax errors. There are of course unbound variable names and such.


#5

I’ll try to use your guards example, it looks great. I’m not sure where my compiler error came from, it said the error was in that block, and it was a syntax error. Thanks for the directions!


#6

I had to modify the logic a bit, going to post it here just in case someone might find it useful in the future:

let className = "question__answer " ++ (switch (selectedAnswer) {
  | Some(_selectedAnswerValue) when answer.correct =>
      "question__answer_correct"
  | Some(selectedAnswerValue) when selectedAnswerValue.text == answer.text && !answer.correct =>
      "question__answer_incorrect"
  | _ =>
      ""
});

Thanks again @glennsl

Edit: Actually, after looking at the generated JS, I’ve noticed the && !answer.correct isn’t needed. The whole switch is compiled down to nested ternary operators, meaning the first answer.correct check is enough.


#7

There is a nice utility library called Belt that can probably help you out here. Pseudo code below:

let className = "question_answer " ++ Belt.Option.(
  flatMap(selectedAnswer, a => a.correct === true 
    ? Some("question__answer_correct") 
    : Some("question__answer_incorrect")
  )
  ->getWithDefault("") 
)

#8

End-of-line comments were added in Reason 3.4.0, January and BuckleScript synced up with that pretty quickly.


#9

Oh, cool. Doesn’t seem to work in the playground though.