I managed to make it work. I have to say this though: it looks like black magic, it's way beyond my understanding.
But it is all necessary
black magic. You do seem to have gotten all the pieces together
correctly. I was getting a similar example together to post, but you
beat me to it.
Here is what I was going to recommend, together with some comments on what the different steps do.
DOCUMENT();
loadMacros( "PGstandard.pl", "Parser.pl", "PGcourse.pl", );
TEXT(beginproblem());
Context("Numeric");
package f; our @ISA = qw(Parser::Function::numeric);
my $a = 1; my $b = -2;
sub f { my $self = shift; my $x = shift; sqrt($x+$a)+$b; }
package main;
sub f {Parser::Function->call('f',@_)} Context()->functions->add(f => {class => 'f', TeX => 'f'});
$g = Formula("f(x+1)");
BEGIN_TEXT \(f(x+1)\) = \{ans_rule(30)\} END_TEXT
ANS($g->cmp);
ENDDOCUMENT();
Here is what the important lines do: package f; our @ISA = qw(Parser::Function::numeric);
The first line starts a new package (called 'f' in this case, but it
could be anything) and the second line makes it a subclass of the
Parser::Function::numeric class, which is the one that implements
functions of the form R->R. This makes this a real-valued function
of one real variable, which is what we want. Our function will inherit
all the behavior of that class (i.e., how to evaluate itself, how to
print its TeX form and its perl form, and so on). This saves us a lot
of work. sub f { my $self = shift; my $x = shift; sqrt($x+$a)+$b; }
This makes the function that actually computes the value of the
function we care about. We could have lots of additional functions here
(that are R->R functions), but we only need one for our purposes.
(The actual Parser::Function::numeric class that we are taking
advantage of defines sin, cos, asin, log, and so on.) package main;
This ends the package and goes back to the "normal" namespace. sub f {Parser::Function->call('f',@_)}
This makes the f(x) function work in perl
expressions. It calls the Parser's function-calling mechanism to look
up the function 'f' and call it with the appropriate parameters. One of
the consequences of this is that if you call f with a formula as its argument, instead of having f try to evaluate itself on the formula, you get back a formula containing f applied to your original formula. E.g.,f(Formula("x+1")) produces the same thing as Formula("f(x+1)") . This is not what would have happened without f being defined this way (see my next message).
Context()->functions->add(f => {class => 'f', TeX => 'f'});
This adds the function f to the current context so that it can actually be included in parsed formulas. Here, "f => " tells the Parser that the name of the function as it will appear in the student's answer (or the string to Formula ) is 'f'. The class => 'f' says that the package that should be used to handle the funciton is package f , which we defined above, and TeX => 'f'
says that it's TeX form should a plain f followed by its arguments.
(Without this, the f would appear in \rm, which is how most named
functions like sin and tan are supposed to be represented).
Once this command is given, "f(...)" can appear in the strings passed to Formula() or in student answers that use this context. So setting $g = Formula("f(x+1)") is now allowed, and would not be if the Context()->functions->add had not been given.
The remainder of code should be self-evident.
An slight modification to this is the following: DOCUMENT();
loadMacros( "PGstandard.pl", "Parser.pl", "PGcourse.pl", );
TEXT(beginproblem());
Context("Numeric"); my $x = Formula('x');
my $a = 1; my $b = -2;
$f = sqrt($x+$a)+$b;
package f; our @ISA = qw(Parser::Function::numeric); sub f {shift; $main::f->eval(x=>shift)} package main; sub f {Parser::Function->call('f',@_)} Context()->functions->add(f => {class => 'f', TeX => 'f'});
$g = f($x+1);
BEGIN_TEXT (f(x+1)) = {ans_rule(30)} END_TEXT
ANS($g->cmp);
ENDDOCUMENT();
There are two main changes in this example: first, the actual value of
the function is not hard-coded into the subroutine in the package, but
is defined in a Parser formula outside the package. (The subroutine
asks the formula stored in $f to evaluate itself with x equal to the value passed to the subroutine.)
Second, rather than calling Formula() directly, the values of $f and $g are produced automatically by the overloaded operators acting on the formula held in $x , which is set to Formula('x')
earlier in the file, so once this formula is generated, all the other
formulas can be produced simply by writing what look like normal perl
expressions.
I include this example mostly as a sample of how to create formulas in
this non-traditional way. You should use whichever form seems most
natural to you (or most convenient at the time). Notice that formulas
produced in this way need not be assigned to variables in order to be
useful. For example, the answer checker could have been produced as
ANS(f($x+1)->cmp);
instead.
The addition of named functions to the
context is, perhaps, more cumbersome than necessary. (On the other
hand, it was not possible at all with the previous parser). I have some ideas about how to simplify the situation.
Davide
<| Post or View Comments |>
|