WeBWorK Problems

parserOneOf feedback messages

parserOneOf feedback messages

by Alex Jordan -
Number of replies: 1
With parserOneOf.pl, suppose you have
$answer = OneOf($a, $b, $c);

where $a, $b, and $c are Math Objects.
Now suppose a student types in some answer that is incorrect. (It does not pass the checker for $a nor for $b nor for $c.) The answer is marked incorrect, and there is no feedback message.

OK, suppose that the checker for $a alone generates one feedback message, the checker for $b generates another feedback message, and the checker for $c generates no feedback message. Is there any way I can access those two feedback messages and return one of them to the student? Ideally, printing the shorter (nonempty) message?

My situation is that I am using contextForm.pl (not yet published) which has a custom answer checker useful for formulas where form matters. It distinguishes 2x/2 from x, but does not distinguish x+1 from 1+x, for example. One deficiency is that it distinguishes 2/3 sqrt(5) from 2sqrt(5)/3, which we would not like it to do. I am essentially looking at using
OneOf(Formula('2/3 sqrt(5)'), Formula('2sqrt(5)/3'))

to get around this. However, when a student enters sqrt(20)/3 or sqrt(20/9), using OneOf prevents the feedback message (from either individual answer) from coming out. In this case, the message is something like "Your answer is numerically correct but not in the expected form."

I'm looking into whether I can modify methods from parserOneOf.pl so that it holds onto the individual feedback messages and uses one of them, chosen in some smart way.


In reply to Alex Jordan

Re: parserOneOf feedback messages

by Davide Cervone -
The issue is coming from the fact that OneOf() does not actually call the answer checkers on the individual answer, but rather does the equivalent of doing a == between the various correct answers and the students answer. This makes a difference because while the answer checker will use == internally, it often also does much more in order to provide additional feedback to students about their answer.

For example, If two vectors with different lengths are compared via ==, the result will be false with no messages of any kind (you would not want such a comparison to throw an error, which is the only mechanism for providing additional messages in that situation). But when a student's answer is compared to a correct answer of ta different length, you do want additional information. So the answer checking is more extensive than just comparisons in general.

The fact that OneOf() does only comparison, not answer checking, means your extra answer messages are never even being produced (unless the messages are produced within the compare() method if you class).

You can subclass the OneOf object to replace the method that does the comparison so that it actually does call the answer checkers rather than just doing comparisons internally. Here is an example:

package my::OneOf;
our @ISA = ('parser::OneOf');

sub cmp_compare {
  my ($self,$other,$ans) = @_;
  foreach $answer (@{$self->{data}}) {
    if ($answer->typeMatch($other)) {
      my $result = $answer->cmp->evaluate($other);
      if ($result->{score}) {
        $ans->score($result->{score});
        $ans->{ans_message} = $result->{ans_message};
        return $ans->{score};
      }
      if ($result->{ans_message}) {
        $ans->{ans_message} = $result->{ans_message}
           if !$ans->{ans_message} || length($ans->{ans_message}) > length($result->{ans_message});
      }
    }
  }
  return 0;
}

package main;

sub myOneOf {my::OneOf->(@_)}
Here, we loop through the possible correct answers and check if the student answer was the correct type to make a comparison with it. (This is because the correct answers could be of different types, e.g., OneOf("<1,2>", "x"), and you don't want to compare the student's answer of x with the vector, as that would generate a type-mismatch error.) If the type is OK, we do the answer checker to see if the student answer is the correct answer. If it is, we record the score, set the answer message to the one for this answer (in case we had one save from a previous incorrect check) and return the score. (If you can give partial credit, you may want to continue looping to see if there is a better answer, but you would have to handle keeping track of incorrect and messages differently).

If the answer isn't correct, then we check if there was an answer message for the incorrect answer. If so, we check if it is shorter than the previous answer message (if there was one), and save it as the answer message if so. That means we will end up with the shortest answer message in the end, unless there was a correct answer, in which case you get the correct answer's message.

It would also be possible simply to record all the answer messages and pas them back through the answer hash so that you could use a post-filter to handle them (or do some extra processing at the end of the cmp_compare() method) to determine the final answer message. Something like

sub cmp_compare {
  my ($self,$other,$ans) = @_;
  $ans->{messages} = [];
  foreach $answer (@{$self->{data}}) {
    if ($answer->typeMatch($other)) {
      my $result = $answer->cmp->evaluate($other);
      push (@{$ans->{messages}}, $result->{ans_message} || '');
      if ($result->{score}) {
        $ans->score($result->{score});
        $ans->{ans_message} = $result->{ans_message};
        return $ans->{score};
      }
    } else {
      push (@{$ans->{messages}}, '');
    }
  }
  return 0;
}
which uses $ans->{message} to store a reference to an array of the answer messages produced (up until the correct answer is found). You can take it from there.

To use the new class, call myOnOf() instead of OneOf().

I hope that does what you need!