Data type of data types
DOM_DOMAIN
is the data type of datatypes.
Each MuPAD^{®} object has a unique data type. Since a data
type is a MuPAD object, too, it must itself have a data type;
the data type comprising all data types (including itself) is DOM_DOMAIN
.
There are two kinds of elements of DOM_DOMAIN
:
data types of the kernel, and data types defined in the library or
by the user (domains). Objects that have a data
type of the latter kind are called domain elements.
A data type has the same internal structure as a table; its entries are called slots. One particular slot is the key; no two different data types can have the same key. Most of the other slots determine how arguments of that data type are handled by functions.
Once a user-defined domain has been constructed, it cannot be destroyed.
Our first example stems from ethnology: some languages in Polynesia do not have words for numbers greater than three; every integer greater than three is denoted by the word "many". Hence two plus two does not equal four but "many". We are going to implement a domain for this kind of integers; in other words, we are going to implement a data type for the finite set {1, 2, 3, many}.
S := newDomain("Polynesian integer")
At this point, we have defined a new data type: a MuPAD object can be a Polynesian integer now. No operations are available yet; the domain consists of its key only:
op(S)
Even though there are no methods for input and output of domain
elements yet, Polynesian integers can be entered and displayed right
now. You have to use the function new
for defining domain elements:
x := new(S, 5)
Now, x
is a Polynesian integer:
type(x)
Of course, MuPAD cannot know what meaning a Polynesian
integer has and what its internal structure should be. The arguments
of the call to the function new
are
just stored as the zeroth, first, etc. operand of the domain element,
without checking them. You may call new
with as many arguments as you want:
new(S, 1, 2, 3, 4); op(%)
new
cannot
know that Polynesian integers should have exactly one operand and
that we want 5
to be replaced by many
.
To achieve this, we implement our own method "new"
;
this also allows us to check the argument. We have one more problem:
domain methods should refer to the domain; but they should not depend
on the fact that the domain is currently stored in S
.
For this purpose, MuPAD has a special local variable dom
that
always refers to the domain a procedure belongs to:
S::new := proc(i : Type::PosInt) begin if args(0) <> 1 then error("There must be exactly one argument") end_if; if i > 3 then new(dom, hold(many)) else new(dom, i) end_if end_proc:
A function call to the domain such as S(5)
now
implicitly calls the "new"
method:
S(5)
S("nonsense")
Error: The object '"nonsense"' is incorrect. The type of argument number 1 must be 'Type::PosInt'. Evaluating: S::new
In the next step, we define our own output method. A Polynesian
integer i
, say, shall not be printed as new(Polynesian
integer, i)
, only its internal value 1
, 2
, 3
,
or many
shall appear on the screen. Note that this
value is the first operand of the data structure:
S::print := proc(x) begin op(x, 1) end_proc: S(1), S(2), S(3), S(4), S(5)
By now, the input and output of elements of S
have
been defined. It remains to define how the functions and operators
of MuPAD should react to Polynesian integers. This is done by overloading them.
However, it is not necessary to overload each of the thousands of
functions of MuPAD; for some of them, the default behavior is
acceptable. For example, expression manipulation functions leave domain
elements unaltered:
x := S(5): expand(x), simplify(x), combine(x); delete x:
Arithmetical operations handle domain elements like identifiers; they automatically apply the associative and commutative law for addition and multiplication:
(S(3) + S(2)) + S(4)
In our case, this is not what we want. So we have to overload
the operator +
.
Operators are overloaded by overloading the corresponding "underline-functions";
hence, we have to write a method "_plus"
:
S::_plus := proc() local argv; begin argv := map([args()], op, 1); if has(argv, hold(many)) then new(dom, hold(many)) else dom(_plus(op(argv))) end_if end_proc:
Now, the sum of Polynesian integers calls this slot:
S(1) + S(2), S(2) + S(3) + S(7)
Deleting the identifier S
does not destroy
our domain. It can still be reconstructed using newDomain
.
delete S: op(newDomain("Polynesian integer"))
We could now give a similar example for more advanced Polynesian
mathematics with numbers up to ten, say. This leads to the question
whether it is necessary to enter all the code again and again whenever
we decide to count a bit farther. It is not; this is one of the advantages
of domain constructors. A domain constructor
may be regarded as a function that returns a domain depending on some
input parameters. It has several additional features. Firstly, the
additional keywords category
and axiom
are
available for specifying the mathematical structure of the domain;
in our case, we have the structure of a commutative semigroup where
different domain elements have different mathematical meanings (we
call this a domain with a canonical representation). Secondly, an
initialization part may be defined that is executed exactly once for
every domain returned by the constructor; it should at least check
the parameters passed to the constructor. Each domain created in such
a way may inherit methods from other domains, and it must at least
inherit the methods of Dom::BaseDomain
.
domain CountingUpTo(n : Type::PosInt) inherits Dom::BaseDomain; category Cat::AbelianSemiGroup; axiom Ax::canonicalRep; new := proc(x : Type::PosInt) begin if args(0) <> 1 then error("There must be exactly one argument") end_if; if x > n then new(dom, hold(many)) else new(dom, x) end_if end_proc; print := proc(x) begin op(x, 1) end_proc; _plus := proc() local argv; begin argv:= map([args()], op, 1); if has(argv, hold(many)) then new(dom, hold(many)) else dom(_plus(op(argv))) end_if end_proc; // initialization part begin if args(0) <> 1 then error("Wrong number of arguments") end_if; end:
Now, CountingUpTo
is a domain constructor:
type(CountingUpTo)
We have defined the domain constructor CountingUpTo
,
but we have not created a domain yet. This is done by calling the
constructor:
CountingUpToNine := CountingUpTo(9); CountingUpToTen := CountingUpTo(10)
We are now able to create, output, and manipulate domain elements as in the previous example:
x := CountingUpToNine(3): y := CountingUpToNine(7): x, x + x, y, x + y, y + y
x := CountingUpToTen(3): y := CountingUpToTen(7): x, x + x, y, x + y, y + y
delete CountingUpToNine, CountingUpToTen, CountingUpTo, x, y:
No domain constructor with the same name may be used again during the same session.
Suppose that your domain does not really depend on a parameter,
but that you need some of the other features of domain constructors.
Then you may define a domain constructor dc
, say,
that is called without parameters. From such a domain constructor,
you can construct exactly one domain dc()
. Instead
of defining the constructor via domain dc() ... end
first
and then using d := dc()
to construct the domain d
,
say, you may directly enter domain d ... end
, thereby
saving some work.
Continuing the previous examples, suppose that we want to count
up to three, knowing that we never want to count farther. However,
we want to declare our domain to be an Abelian semigroup with a canonical
representation of the elements. This is not possible with a construction
of the domain using newDomain
as in Example 1: we have to use the keyword domain. You will notice at once
that the following source code is almost identical to the one in the
previous example—we just removed the dependence on the parameter n
.
domain CountingUpToThree inherits Dom::BaseDomain; category Cat::AbelianSemiGroup; axiom Ax::canonicalRep; new := proc(x : Type::PosInt) begin if args(0) <> 1 then error("There must be exactly one argument") end_if; if x > 3 then new(dom, hold(many)) else new(dom, x) end_if end_proc; print := proc(x) begin op(x, 1) end_proc; _plus := proc() local argv; begin argv:= map([args()], op, 1); if has(argv, hold(many)) then new(dom, hold(many)) else dom(_plus(op(argv))) end_if end_proc; end:
Now, CountingUpToThree
is a domain and not
a domain contructor:
type(CountingUpToThree)
You may use this domain in the same way as CountingUpTo(3)
in Example 2.
When called as a function, the data type creates a new object
of this data type out of the arguments of the call. E.g., the call DOM_LIST(1,
2, x)
generates the list [1, 2, x]
of
domain type DOM_LIST
(although,
in this case, you probably prefer to type in [1, 2, x]
directly
which results in the same object). It depends on the particular type
which arguments are admitted here.
In the case of a domain, the "new"
method
of that domain is called.
You can obtain the slots of a domain using slot
. The function slot
can also be used on the left hand
side of an assignment to define new slots, or to re-define existing
slots. Use delete
to
delete slots.
A data type consists of an arbitrary number of equations (objects
of type "equal"
). If a = b
is
among these equations, we say that the slota
of
the data type equals b
. By convention, a
is
usually a string. Each domain has at least one slot indexed by "key"
.
The names of the data types provided by the MuPAD kernel
are of the form DOM_XXX
, such us DOM_ARRAY
, DOM_HFARRAY
, DOM_IDENT
, DOM_INT
, DOM_LIST
, DOM_TABLE
etc.
You can create further data types using the function newDomain
(cf. Example 1) or via the keyword domain (cf. Example 3).
You can also create new data types by calling a domain constructor. Various pre-defined domain constructors can be found in the library Dom. You can also define your own domain constructors using the keyword domain. Cf. Example 2.
The domain type (data type) of any MuPAD object can be
queried by the function domtype
.
Only one domain with a given key may exist. If it is stored
in two variables S
and T
, say,
assigning or deleting a slot slot(S, a)
implicitly
also changes slot(T, a)
(reference effect). This
also holds if a = "key"
.
Note:
You get no warning even if |