Application Center - Maplesoft

App Preview:

Warnings PowerTool

You can switch back to the summary page by clicking here.

Learn about Maple
Download Application


 

warnings_powertool.mws

The Warnings Package

Douglas Wilhelm Harder, July 29th, 2002

> restart;

Students new to Maple often make mistakes such as:

1.  Using equals instead of assignment, e.g., x = 3; instead of x := 3;.
2.  Using
pi and i instead of Pi and I, respectively.
3.  Using remember table assignment instead of operator assignment, e.g.,
f(x) := x^2; instead of f := x -> x^2;
4.  Forgetting multiplication signs which do not cause errors, e.g.,
x(x - 1)(x + 1) instead of x*(x - 1)*(x + 1).
5.  Using short cuts in entering functions, e.g.,
sinx instead of sin(x).
6.  Using matrix and vector instead of Matrix and Vector.

These mistakes can be difficult to find.  The Warnings package gives feedback to the user immediately after the expression is evaluated.  After saving this package to a library or executing this worksheet, the user can enter:

> Warnings:-StartWarnings();

Then, when something suspicious arises, a Maplet appears with the warning.  The first text box on the Maplet gives what the expression evaluated to, and the second gives a possible correction.  Each type of correction can be turned off by clicking the check box at the bottom of the Maplet or by using the Understood export of the Warnings package.  StopWarnings turns the warnings off and ResetWarnings turns all warnings back on.

For efficiency purposes, if the output is a 2D or 3D plot, the evaluated object is not inspected.


When the user presses
Enter, the evaluated result is inspected for problems like those listed above.  While it is possible to make changes to the evaluation, it is probably better to alert the user to the problem and have them go back and manually check it.  Otherwise, they will continue to use this feature instead of learning the correct syntax.

The package is aware of all the errors listed above by using the
AddWarning export of the package.  The package can be extended using the same routine.  AddWarning takes two arguments, the first is a symbol which must be unique, and the second is a procedure which takes as input one argument which is a list of the result of the user's evaluation.  The output must be an expression sequence of two strings if a problem was found or false otherwise.  For example, the following routine returns a warning if the user uses f instead of F:

Warnings[AddWarning](
 useF,

 proc(input)

   if has( input, 'f' ) then

     "You used the varible f.  You should use F:",

     sprintf( "%q;", op( subs( 'f' = 'F', [args] ) ) );

   else

     'false';

   end if;

 end proc

);

This routine also takes a few additional options:
You may select a non-default title using the
title = "...." option.
A check is made to ensure you do not add a warning with the same unique identifier as one already in the database.  You can turn this check off by explicitly giving the option
check = false.

Note, since warnings are suppose to help the student, if the procedure included with AddWarning ever generates an error or invalid output, that problem is silently ignored.  The student is having enough problems as it is without having to deal with problems caused by the author.  In order to see these errors, you can use the option report_errors = true when you call StartWarnings.

The exports UserNames and UserFunctions return a set of all non-library names and function names which have been used up until that point.

Warnings Source Code

> Warnings := module()
 option `Copyright (c) 2002 by Douglas Wilhelm Harder.  All rights reserved.`;


 export AddWarning, StartWarnings, StopWarnings, ResetWarnings,

   Understood, UserNames, UserFunctions;


 local

   isWarning::truefalse,

   reportErrors::truefalse,

   userNameSet::set(name),

   userFunctionSet::set(name),

   tmpEval,


   fixTab::table,

   titleTab::table,

   warningTab::table,


   warning::procedure,


   # Process Option Default Tables

   AddWarningDefaults::table,

   StartWarningsDefaults::table;


 isWarning := 'false';

 reportErrors := 'false';

 userNameSet := {};

 userFunctionSet := {};


 AddWarningDefaults := table( [

   OptionParms = table( [

     'title' = ["Warning", {'string', 'symbol'}],

     'check' = ['true', 'truefalse']

   ] ), PositionalParms = table()

 ] );


 AddWarning := proc( key::symbol, fix::procedure )

   local others, Options;


   others := [ProcessOptions( 2, [args], AddWarningDefaults, Options )];


   if others <> [] then

     error "unexpected option(s): %0", op( others );

   end if;


   # Normally, do not allow the user to use a key already used by

   # another warning.  This can be overridden by the use of the

   # option 'check' = 'false'.  'Check' is 'true' by default.


   if Options['check'] and assigned( warningTab[key] ) then

     error "the key %1 is already assigned", key;

   end if;


   # Check the routine for two simple cases which are bound

   # to come up.


   try

     fix([]);

   catch:

     error "the 2nd argument fails with NULL user input";

   end try;


   try

     fix([1, 2]);

   catch:

     error "the 2nd argument fails with an expression sequence";

   end try;


   # Assign to the tables


   warningTab[key] := 'true';

   fixTab[key] := fix;

   titleTab[key] := sprintf( "%s (%a)", convert( Options['title'], 'string' ), key );


   NULL;

 end proc;


 Understood := proc()

   local i, unknowns;


   # All warning keywords are symbols, and this takes no other

   # options


   if not type( [args], 'list'('symbol') ) then

     error "unexpected option(s): %0",

       seq( `if`( i::'symbol', NULL, i ), i = [args] );

   end if;


   # For each symbol, check if it's a valid warning.

   # If so, no longer issue any related warnings.

   # At the end, any invalid warnings are returned as

   # an error.  Note, this is quadradic is space if and

   # only if the user makes an error. :)


   unknowns := NULL;


   for i in [args] do

     if assigned( warningTab[i] ) then

       warningTab[i] := 'false';

     else

       unknowns := unknowns, i;

     end if;

   end do;


   if unknowns = NULL then

     NULL;

   else

     error "unknown warning(s): %0", unknowns;

   end if;

 end proc;


 StartWarningsDefaults := table( [

   OptionParms = table( [

     report_errors = [false, truefalse]

   ] ), PositionalParms = table()

 ] );


 StartWarnings := proc()

   local others, Options;


   others := [ProcessOptions( 0, [args], StartWarningsDefaults, Options )];


   if others <> [] then

     error "unexpected option(s): %0", op( others );

   end if;


   # The user can indicate that errors in the warning

   # procedures be reported.


   reportErrors := Options['report_errors'];


   # If warnings are already turned on, do nothing.

   # Save any old evaluator.


   if not isWarning then

     tmpEval := kernelopts( Evaluator );


     Maplets:-Tools:-StartEngine();


     if tmpEval = NULL then

       tmpEval := proc() args end proc;

     end if;


     kernelopts( Evaluator = warning@tmpEval );


     isWarning := 'true';

   end if;


   NULL;

 end proc;


 StopWarnings := proc()

   # If warnings are already off, do nothing


   if iswarning then

     kernelopts( Evaluator = eval( tmpEval, 1 ) );

     isWarning := 'false';

   end if;


   NULL;

 end proc;


 ResetWarnings := proc()

   local i;


   # Reset so that warnings are given for all cases


   for i in {indices( warningTab )} do

     warningTab[op( i )] := 'true';

   end do;


   NULL;

 end proc;


 UserNames := proc()

   userNameSet;

 end proc;


 UserFunctions := proc()

   userFunctionSet;

 end proc;


 warning := proc()

   local i, maplet, result, fix, largs, problem_found;


   largs := [args];

   problem_found := 'false';


   # Do not check plots (for now)


   if type( largs, ['specfunc'('anything', {'PLOT', 'PLOT3D'} )] ) then

     return args;

   end if;


   for i in {indices( warningTab )} do

     if warningTab[op( i )] then

       # Try calling the routine fixTab[op( i )].

       # If an error occurs, if such errors are to be reported

       # (reportErrors = true) then issue an error, otherwise

       # silently ignore the problem (you don't want to annoy

       # students with errors caused by the programmer.)


       try

         fix := fixTab[op( i )]( largs );

       catch:

         if reportErrors = 'true' then

           error "error from warning `%1`: %2",

             op( i ),

             StringTools:-FormatMessage( lastexception[2..-1] );

         else

           next;

         end if;

       end try;


       if fix = 'false' then

         next;

       elif not type( [fix], ['string', 'string'] ) then

         error "warning `%1` did not return `false` or a pair of strings: %2",

           op( i ), [fix];

       end if;


       problem_found := 'true';


       use Maplets:-Elements in

         maplet := Maplet(

           'onstartup' = "A0",


           Action( 'reference' = "A0", RunWindow( 'window' = "W1" ) ),


           Window( 'reference' = "W1",

             'title' = titleTab[op( i )],

             'layout' = "BL1"

           ),


           BoxLayout( 'reference' = "BL1",

             BoxColumn(

               BoxCell( 'value' = "L1" ),

               BoxCell( 'value' = "TB1" ),

               BoxCell( 'value' = "L2" ),

               BoxCell( 'value' = "TB2" ),

               BoxCell( 'value' = "ChB" ),

               BoxCell( 'value' = "B" )

             )

           ),


           Label( 'reference' = "L1", 'caption' = "The expression you entered evaluated to:" ),

           TextBox( 'reference' = "TB1", 'value' = sprintf( "%q;", args ) ),

           Label( 'reference' = "L2", 'caption' = fix[1] ),

           TextBox( 'reference' = "TB2", 'value' = fix[2] ),

           CheckBox( 'reference' = "ChB", 'value' = 'false',

             'caption' = "Do not show this warning again."

           ),

           Button( 'reference' = "B", 'caption' = "OK", 'onclick' = "A1" ),


           Action( 'reference' = "A1",

             Shutdown( '`return`' = "R1" )

           ),


           Return( 'reference' = "R1", ReturnItem( 'item' = "ChB" ) )

         );

       end use;


       result := Maplets:-Display( maplet );


       # If the user clicked on 'Do not show this warning again.'

       # then turn that warning off.


       if result = ["true"] then

         warningTab[op( i )] := 'false';

       end if;

     end if;

   end do;


   # Only add new names or functions to the sets if

   # no problems were found.


   if not problem_found then

     userNameSet := userNameSet union remove(

       'type',

       indets( largs, 'name' ),

       'stdlib'

     );

     userFunctionSet := userFunctionSet union remove(

       'type',

       map2( 'op', 0, indets( largs, 'typefunc'('anything', 'name') ) ),

       'stdlib'

     );

   end if;


   args;

 end proc;

end module:

> with(Warnings);

[AddWarning, ResetWarnings, StartWarnings, StopWarnings, Understood, UserFunctions, UserNames]

p = x^2 + 2*x + 3

> AddWarning(
 'assignment',

 proc(input)

   local n, i, format;


   if type( input, ['name' = 'anything'] ) then

     "Perhaps you meant to use assignment:",

     sprintf( "%a := %q;", op( [1, 1], input ), op( [1, 2], input ) );

   else

     false;

   end if;

 end proc

):

f(x) = x^2 + 3;

> AddWarning(
 function_assignment,

 proc(input)

   local n, i, format;


   if type( input, ['function'('name') = 'anything'] ) then

     n := nops( lhs( input[1] ) );


     if n = 0 then

       format := "%a := () -> %a";

     else

       format := cat( "%a := (%a", seq( ", %a", i = 1..n - 1 ), ") -> %a;" );

     end if;


     "Perhaps you meant operator assignment:",

     sprintf( format, op([1,1,0], input), op([1,1,1..-1], input), op([1,2], input) );

   else

     false;

   end if;

 end proc

):

x (x + 3)

> AddWarning(
 multiplication,

 proc(input)

   local nms, fns, oddfns;


   nms := indets( input, 'name' ) union Warnings:-UserNames();

   fns := indets( input, 'function' );

   fns, oddfns := selectremove( 'type', fns, 'typefunc'('anything', 'name') );


   fns := select(

     proc(x) member( op( 0, x ), nms ) end proc,

     fns

   );


   fns := map( x -> x = op( 0, x ) * op( x ), fns union oddfns );


   if nops( fns ) = 0 then

     'false';

   else

     "Did you forget to use the multiplication sign?",

     sprintf( "%q", op( subs( fns, input ) ) );

   end if;

 end proc,

 check = false

):

sin^3(x + 1), sin^n(x + 1)

> AddWarning(
 function_powers,

 proc(input)

   local mfs, sT;


   mfs := indets( input, 'mathfunc'^'algebraic' );


   if nops( mfs ) > 0 then

     if type( op( [1, 2], mfs ), 'typefunc'('anything', 'algebraic') ) then

       sT := sprintf( "%q", op( [1, 2, 1..-1], mfs ) );


       sT := sprintf( "%a(%s)^%a", op( [1, 1], mfs ), sT, op( [1, 2, 0], mfs ) );

     else

       sT := sprintf( "%a(t)^%a", op( [1, 1], mfs ), op( [1, 2], mfs ) );

     end if;


     "Functions should be raised to a power as follows:", sT;

   else

     'false';

   end if;

 end proc

);

f(x) := x^2;

> AddWarning(
 'remember_table_assignment',

 proc(input)

   local fns, ifns, i4fns, p, nm, var, expr, varS, exprS;


   fns := [op( indets( input, 'typefunc'('anything', 'name') ) )];

   fns := map2( 'op', 0, fns );

   fns := remove( 'type', fns, 'mathfunc' );

   fns := select( 'type', fns, 'procedure' );

   ifns := map( 'ToInert'@'eval', fns );

   i4fns := map2( subsop, 4 = NULL, ifns );


   if not member( _Inert_PROC(

     _Inert_PARAMSEQ(),

     _Inert_LOCALSEQ(),

     _Inert_OPTIONSEQ(

       _Inert_NAME("remember")

     ),

     _Inert_STATSEQ(

       _Inert_UNEVAL(

         _Inert_FUNCTION(

           _Inert_PROCNAME(),

           _Inert_EXPSEQ(_Inert_ARGS())

         )

       )

     ),

     _Inert_DESCRIPTIONSEQ(),

     _Inert_GLOBALSEQ(),

     _Inert_LEXICALSEQ()

   ), i4fns, p ) then

     return 'false';

   end if;


   nm := op( p, fns );

   var := FromInert( op( [p, 4, 1, 2, 1], ifns ) );

   expr := FromInert( op( [p, 4, 1, 2, 2], ifns ) );


   varS := sprintf( "(%q)", var );

   exprS := sprintf( "%q", expr );


   "Some evaluation did not occur.  Perhaps you previously meant to do:",

   sprintf( "%a := %s -> %s;", nm, varS, exprS );

 end proc,

 check = false

):    

pi

> AddWarning(
 pi,

 proc(input)

   if has( input, 'pi' ) then

     "You used pi, perhaps you meant to use Pi (= 3.1415...).",

     sprintf( "%q;", op( subs( 'pi' = 'Pi', input ) ) );

   else

     'false';

   end if;

 end proc

):

i

> AddWarning(
 imaginary,

 proc(input)

   local i, iu, au, aus;


   i := interface( 'imaginaryunit' );


   iu := convert( i, 'string' );


   if member( iu, {"I", "J"} ) then

     au := StringTools:-LowerCase( iu );

   elif member( iu, {"i", "j"} ) then

     au := StringTools:-UpperCase( iu );

   else

     return 'false';

   end if;       


   aus := convert( au, 'symbol' );


   if has( input, aus ) then

     sprintf( "You used %s, perhaps you meant to use %s (= sqrt(-1)).", au, iu ),

     sprintf( "%q;", op( subs( aus = i, input ) ) );

   else

     'false';

   end if;

 end proc,

 check = false

):

sinx, cosx, etc.

> AddWarning(
 sinx,

 proc(input)

   local syms, mfns, i, j, si, A, B;


   syms := indets( input, 'symbol' );


   mfns := NULL;


   for i in syms do

     for j from length( i ) - 1 to 2 by -1 do

       si := substring( i, 1..j );


       if type( si, 'mathfunc' ) then

         mfns := mfns, i = subs(

           [A = si, B = substring( i, length( si ) + 1.. -1 )],

           A(B)

         );


         next;

       end if;

     end do;

   end do;


   if mfns = NULL then

     'false';

   else

     sprintf(

       "Did you mean to use, for example, %a instead of `%a`:",

       op( [1, 2], [mfns] ), op( [1, 1], [mfns] )

     ),

     sprintf( "%q;", op( subs( mfns, input ) ) );

   end if;

 end proc,

 'check' = 'false'

):

matrix and vector

> AddWarning(
 linalg,

 proc(input)

   local matrices, vectors;


   matrices := indets( input, 'matrix' );

   vectors := indets( input, 'vector' );


   if hastype( input, 'matrix' ) or hastype( input, 'vector' ) then

     "You may have meant to use `Matrix` and `Vector` instead of `matrix` and `vector`.",

     sprintf(

       "%q;",

       op( subsindets( input, {'matrix', 'vector'}, proc(x)

         if type( x, 'matrix' ) then

           'Matrix'( convert( x, 'listlist' ) );

         else

           'Vector'( convert( x, 'list' ) );

         end if;

       end proc ) )

     );

   else

     'false';

   end if;

 end proc,

 'check' = 'false'

):

> StartWarnings():

> t = 3;

t = 3

> f(x) = t^2;

f(x) = t^2

> x(x + 3);

x(x+3)

> (3+x)(x + 3);

x(x+3)+3

> y(x + y + 3);

y(x+y+3)

> y(x); # Okay

y(x)

> x(x+3)(x+5);

x(x+3)(x+5)

> (sin^3)(x); # Okay

sin(x)^3

> sin^2(x);

sin^2

> sin^n(x + 3);

sin^n(x+3)

This cannot be caught:

> f(t) := t^2;

f(t) := t^2

But the aftershocks can:

> f(3); # Probably unexpected

f(3)

> 3 + pi * i^2;

3+pi*i^2

> sinx + cosx;

sinx+cosx

> matrix( [[3,2],[5,3]] );

matrix([[3, 2], [5, 3]])

>

>