Data Type Coercion - Maple Help

Data Type Coercion

In Maple, procedures can be declared with strict types on their parameters.  Often there are multiple data types that are similar enough that a procedure should be able to deal with any of them.  However having to code the procedure to be able to handle each different data type can be complex.  To help with writing such procedures, Maple has data type coercion.  That is, the ability to convert various data types into one expected type.

The most common way to declare that a parameter can be coercible is to put a tilde (~) in front of a standard Maple type, for example m::~Matrix instead of m::Matrix.  If the command ~Matrix exists as either an export of the current module, or a top-level command, then that command will be invoked when the input does not match the type Matrix.

 > p := proc( m::~Matrix )    whattype( m ); end;
 ${p}{:=}{\mathbf{proc}}\left({m}{::}{\mathrm{~Matrix}}\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathrm{whattype}}{}\left({m}\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{end proc}}$ (1)

The above procedure is able to work on various different data types while only seeing the expected type within the function.

 > p( Array( [ [ 1, 2 ], [ 3, 4 ] ] ) );
 ${\mathrm{Matrix}}$ (2)
 > p( Vector[row]( [ 5,6,7 ] ) );
 ${\mathrm{Matrix}}$ (3)
 > p( [ [ 1,2,3 ], [ 4,5,6 ], [7,8,9] ] );
 ${\mathrm{Matrix}}$ (4)

If the given type cannot be converted to the expected type, then an exception is raised.

 > p( 1.4 );

Currently Maple has implementations of ~Matrix, ~Vector, and ~Array.  Refer to these individual help pages to see what data types are accepted by these functions.   It is anticipated that more top-level coercion commands will be implemented in future versions of Maple.  For standard Maple types, it is advised that you either use the long form described below, or use a module export to define your own coercions.  The following example shows how to declare a private coercion routine inside a module.  The use of ModuleApply makes the module work the same way as a procedure, but because it is a module, other methods and data can be associated with it.

 > Frac := module()     local ModuleApply, ~rational;     ~rational := proc( a::float ) convert(a,rational); end;     ModuleApply := proc( r::~rational ) frac(r); end; end module;
 ${\mathrm{Frac}}{:=}{\mathbf{module}}\left({}\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{local}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathrm{ModuleApply}}{,}{\mathrm{~rational}}{;}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{end module}}$ (5)
 > Frac(1.5);
 $\frac{{1}}{{2}}$ (6)
 > Frac(3/2);
 $\frac{{1}}{{2}}$ (7)

Coercion is able to make changes not just to the container, but it will also adjust the data type of rtables to match the expected data types, when possible.

 > p_type := proc( m::~Matrix( datatype=float ) )    rtable_options(m,datatype); end;
 ${\mathrm{p_type}}{:=}{\mathbf{proc}}\left({m}{::}\left({\mathrm{~Matrix}}{}\left({\mathrm{datatype}}{=}{\mathrm{float}}\right)\right)\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathrm{rtable_options}}{}\left({m}{,}{\mathrm{datatype}}\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{end proc}}$ (8)
 > p_type( Matrix( [[1,2],[3,4]], datatype=integer ) );
 ${{\mathrm{float}}}_{{8}}$ (9)
 > p_type( Matrix( [[1/2,2/3],[3/4,4/5]], datatype=rational ) );
 ${{\mathrm{float}}}_{{8}}$ (10)

If the given expression cannot be converted to the expected type, then an exception is raised.

 > p_type( Matrix( [[1*I,2*I],[3*I,4*I]], datatype=complex ) );

Many of the data type coercions from one form of an rtable to another can be performed without actually creating a copy of the data.  Maple can create an alias to the same data set that makes it appear to fit the desired type.  However for some of the conversions, lists to rtables or changing rtable types a copy of the data is necessary.  When a copy is created, modifying the rtable within the procedure will not effect the rtable that was actually passed.  Therefore procedures that intend to work in-place should disable automatic coercion.

A value of NULL returned from the ~ procedure doing the coercion indicates the coercion failed.  This will result in the standard invalid input error from the outer procedure.

 > ~Array(4);

Calling ~Array directly with an integer as input results in NULL because this is not a valid coercion.  Likewise, passing 4 to a procedure that declares its first parameter as ~Array will invoke ~Array(4) behind the scenes, get NULL, and decide that 4 could not be coerced so an error should be raised.

 > proc( a::~Array ) a; end(4);

coerce() Long Form

Maple also allows programmers to explicitly specify how they want coercions to occur.  This is done by using the coerce() parameter modifier. The coerce modifier allows a programmer to specify a sequence of types and coercion procedures.  A coercion procedure is a procedure that accepts a single typed parameter and converts that parameter into a new expression. When the main procedure is called the argument is type checked against the coercion procedure's parameter types.  The first coercion procedure whose parameter's type matches the type of the argument is called.  This is similar to how overloaded procedures work.  The return value of the matching coercion procedure is then used as the parameter's value.

 > p_string := proc( s::coerce( string, (s::name)->convert(s,string) )  )    s; end;
 ${\mathrm{p_string}}{:=}{\mathbf{proc}}\left({s}{::}\left({\mathrm{coerce}}{}\left({\mathrm{string}}{,}{s}{::}{\mathrm{name}}{→}{\mathrm{convert}}{}\left({s}{,}{\mathrm{string}}\right)\right)\right)\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{s}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{end proc}}$ (11)

If the parameter passed is of type string, then it matches the type listed in the coerce statement and thus passes this string through unchanged.

 > p_string( "a string" );
 ${"a string"}$ (12)

If the parameter passed is of type name, the second item, (s::name)->convert(s,string) converts the name to a string and that string is passed as p_string's argument.

 > p_string( a name );
 ${"a name"}$ (13)

If there is no matching coercion procedure, then an error is raised

 > p_string( 1123 );

The previous example uses the arrow form of a procedure declaration, however an named procedure can also be used.

 > p_proc := proc( A::coerce( Matrix, LinearAlgebra:-Simplify(a) ) )    A; end proc:
 > p_proc( x + <1,2;3,4> );
 $\left[\begin{array}{cc}{1}{+}{x}& {2}\\ {3}& {4}{+}{x}\end{array}\right]$ (14)

Note that LinearAlgebra:-Simplify doesn't always return a Matrix, so p_proc must expect to handle these other types.  Alternately you could wrap the call in another procedure that checks its return type and returns NULL if it wasn't the type you wanted.

 > LASimplify := proc(a)    local r := LinearAlgebra:-Simplify(a);    if r::Matrix then        return r;    else        return NULL;    end if; end proc;
 ${\mathrm{LASimplify}}{:=}{\mathbf{proc}}\left({a}\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{local}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{r}{;}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{r}{:=}{\mathrm{LinearAlgebra}}{:-}{\mathrm{Simplify}}{}\left({a}\right){;}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{if}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{r}{::}{\mathrm{Matrix}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{then}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{return}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{r}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{else}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{return}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathrm{NULL}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{end if}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{end proc}}$ (15)
 > p_proc := proc( A::coerce( Matrix, LASimplify ) )    A; end proc:
 > p_proc( a_symbol );

Multiple types and coercion routines can be specified.  They are tested in order.

 > ModNearestP := proc( a::integer, p::coerce( prime, (a::posint)->nextprime(a), (a::negint)->if(type(-a,prime),-a,nextprime(-a)) ) )   a mod p; end proc;
 ${\mathrm{ModNearestP}}{:=}{\mathbf{proc}}\left({a}{::}{\mathrm{integer}}{,}{p}{::}\left({\mathrm{coerce}}{}\left({\mathrm{prime}}{,}{a}{::}{\mathrm{posint}}{→}{\mathrm{nextprime}}{}\left({a}\right){,}{a}{::}{\mathrm{negint}}{→}{\mathrm{if}}{}\left({\mathrm{type}}{}\left({−}{a}{,}{\mathrm{prime}}\right){,}{−}{a}{,}{\mathrm{nextprime}}{}\left({−}{a}\right)\right)\right)\right)\right)\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{a}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{mod}}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{p}\phantom{\rule[-0.0ex]{0.5em}{0.0ex}}{\mathbf{end proc}}$ (16)
 > ModNearestP(5,3);
 ${2}$ (17)
 > ModNearestP(5,4);
 ${0}$ (18)
 > ModNearestP(5,-3);
 ${2}$ (19)

One powerful aspect of the ~Array coercion method is that it allows the procedure author to work using either 1-based or 0-based arrays.  Using the coercion declaration A::~Array(0..) will ensure the procedure always gets an array with index A[0] as its first element.  This ~ shortcut can be spelled out in long form as follows: A::coerce( {Array(0..)}, ~Array(0..) ).  Here, if the passed input does not match the type {Array(0..)}, then ~Array(input,0..) will be invoked to do the coercion.  There is a notable special case in this declaration; we cannot declare this as A::coerce( Array(0..), ~Array(0..) ) because Array(0..) is ambiguous as a coercion function or as a type.  As a rule, all functional forms are treated as coercion functions.  Putting curly brackets around {Array(0..)} forces it to be recognized as a type.