## `DOM_DOMAIN` -- the data type of data types

#### Introduction

Each MuPAD object is of 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.

#### Construction

The names of the data types provided by the MuPAD kernel are of the form `DOM_XXX`, such us `DOM_ARRAY`, `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`.

#### Important Operations

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.

#### Result of Evaluation

Evaluating an object of type `DOM_DOMAIN` returns itself.

#### Function Call

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.

#### Operands

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"`.

#### Example 1

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")

                                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)

                           "key" = "Polynesian integer"

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)

                            new(Polynesian integer, 5)

Now, `x` is a Polynesian integer:

    >> type(x)

                                Polynesian integer

Of course, MuPAD cannot know what a Polynesian integer stands for 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(Polynesian integer, 1, 2, 3, 4)

                                    1, 2, 3, 4

`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)

                           new(Polynesian integer, many)

    >> S("nonsense")

          Error: Wrong type of 1. argument (type 'Type::PosInt' expected\
          ,
                 got argument '"nonsense"');
          during evaluation of '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)

                                1, 2, 3, many, many

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:

                                 many, many, many

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)

                                   many + 2 + 3

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)

                                      3, many

Deleting the identifier `S` does not destroy our domain. It can still be reconstructed using `newDomain`.

    >> delete S: op(newDomain("Polynesian integer"))

          "_plus" = proc S::_plus() ... end,

             "print" = proc S::print(x) ... end,

             "new" = proc S::new(i) ... end, "key" = "Polynesian integer"

#### Example 2

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`. You find more detailed information in the domains reference.

    >> 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)

                                 DomainConstructor

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)

                                  CountingUpTo(9)

                                 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

                                3, 6, 7, many, many

    >> x := CountingUpToTen(3): y := CountingUpToTen(7):
       x, x + x, y, x + y, y + y

                                 3, 6, 7, 10, many

    >> delete CountingUpToNine, CountingUpToTen, CountingUpTo, x, y:

No domain constructor with the same name may be used again during the same session.

#### Example 3

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)

                                    DOM_DOMAIN

You may use this domain in the same way as `CountingUpTo(3)` in example 2.

#### Background

- 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"`.

  You get no warning even if `T` is protected.

Do you have questions or comments?
Copyright © SciFace Software GmbH & Co. KG 2000