## Documentation Center |

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 type of argument number 1 must be 'Type::PosInt'. The object '"nonsense"' is incorrect. 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 *slot*`a` 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`.

Was this topic helpful?