Supers; A Whole Level Beyond Types

Introduction

Super types or simply supers are parametric types. Read Super Types section under Units chapter for theoretical explanations. In this chapter, we focus on practical details of defining and using supers.

Effects on the projects

Each super results in a PHP class in the Server project, extending the abstract class Super or another Super.
Also, the same happens in the Marshal library with respect to the language. However, the front-end clone of the super does not contain the predicate and is only accessible in the MUU (Marshal/Unmarshal Unit).

Terminology

Assume $A$ is a set, the predicate $P$ is called characteristic predicate of $A$ if $P(x) \iff x \in A$. We can use set $A$ and predicate $P$ alternatively since they both carry the same concept.
A type is a set occupied with functions $M$, $U$, and $T$. Read Super Types section under Units chapter to see what these objects are. We usually show a type set by its characteristic predicate instead of the set itself.
Assume $T = (P, Y, M, U)$. We say type $T$ explicitly accepts value $x$ if $P(x)$ is true, and say type $T$ implicitly accepts value $x$, if $P(x)$ is true or $P(U(x))$ is true. If type $T$ explicitly (implicitly) accepts $x$, we say $x$ explicitly (implicitly) fits in $T$. As a convention, when we say $T$ accept $x$, we mean $T$ implicitly accepts $x$, and the same is true for fitting.
A Super Type or simply Super is a function that receives some arguments and returns a type. This type is called an instance of the Super Type.

Structure

A super block definition has the following structure.
# Path\To\Super\SuperName
 
# Description of this Super, probably in markdown formatting
 
Super [extends Path\To\Another\Super\SuperName] {
  [arguments {
    [const] argName0 : Type0 [= DEFAULT_0]; # Description of argName0
    ...                                   ; # ...
    [const] argNameN : TypeN [= DEFAULT_N]; # Description of argNameN      
  }]
  [ validation { VALIDATION_BODY } ]
  predicate { PREDICATE_BODY }
  [ type<TARGET_0> { TYPE_0_BODY } ]
  [ marshal<TARGET_0> { MARSHAL_0_BODY } ]
  [ unmarshal<TARGET_0> { UNMARSHAL_0_BODY } ]
  ...
  [ type<TARGET_N> { TYPE_N_BODY } ]
  [ marshal<TARGET_N> { MARSHAL_N_BODY } ]
  [ unmarshal<TARGET_N> { UNMARSHAL_N_BODY } ]
}
Note that there is no name for the Super. A Super name is its file path. Each file can contain at most one block, and the block inherits its name from the file. It is a good practice to have a comment in the first line of the file describing where the file is located.

Arguments

It is possible to define a set of arguments for a Super. The resulting type is based on those arguments. For each argument, it is possible to set a default value, and it is mandatory to set a type. However, the only way to mention a type is to call a super that returns a type, and we need types to define supers. So how to define the first Super? The answer is we do not NEED types to define a super since we do not need the super to have any argument. This fact leads us to the very first Super, called Universal.
It is possible to define some arguments as constant, which means the programmer cannot pass that argument to the Super. This feature is useful when defining a child super. Read more in Hierarchy.
# \Xua\Supers\Universal
Super {
    Predicate {
        return true;
    }
}
After this, we may use the Universal Super to define other Supers. Although, we may define other Supers without using arguments, such as Boolean and Trilean.

Validation

The validation block is responsible for checking if arguments passed to the Super meet desired conditions. It is an optional Block, and if it is not provided, any argument that fits in the corresponding type will be accepted.
The body is written in pure PHP. All super arguments are available as PHP variables $this->argName0, ..., $this->argNameN, There is also another available variable $exception, which has a method called setError, and one can use it to add an error if some wanted conditions are not met.

Predicate

The Predicate is the main and only required block of a super. This block defines the characteristic predicate.
The body is written in pure PHP. All super arguments are available as PHP variables $this->argName0, ..., $this->argNameN, along with two extra variables $input and $message. The variable $input contains the value which the predicate should check. The value true must be returned if $input explicitly fits in the type, and false otherwise.
When returning false, it is possible to provide a reason of why $input failed to fit in the type. The $message variable is responsible for storing this reason.

Marshal & Unmarshal

Blocks Marshal and Unmarshal can be written for any supported language. Therefore these block names are valid: marshal<php>, marshal<database>, marshal<dart>, marshal<php>, marshal<java>, marshal<javascript>, marshal<kotlin>, marshal<objectivec>, and marshal<swift>. The body of database blocks, is written in PHP, but the methods are used for database store and restore procedures.
Any unwritten block is assumed to be the identity function.
The function of the marshal block is to cast a given value into a value that can be transmitted on the network or stored in the database (usually string, integer, or a stream of bytes) in an invertible way. The unmarshal does the inverse of the marshal function.
When the language is PHP (target is php or database), all Super arguments are available as PHP variables $this->argName0, ..., $this->argNameN, along with an extra variable $input, and the marshaled/unmarshaled data must be returned. When calling Marshal, it is guaranteed that $input explicitly fits in the type. But when calling unmarshal, it is possible that $input is not a valid input; in that case, the convention is to return the input value itself.
For other languages, the same concept holds with respect to the language syntax.

Native Type

There are some scenarios where we need to declare a type (the result of calling a Super) in another language. For example, if we use a type as the type of an Entity field, we must tell the database server to store the values. This declaration is the job of the Type block. All super arguments are available as PHP variables $this->argName0, ..., $this->argNameN. And the block must either return a string identifying the database type (for example, VARCHAR(100)) or null value, which means the type is not available in the specified target. If the block is not provided, the null return value is assumed.

Hierarchy

Supers can come in a hierarchy just like PHP classes, and each block is a class method. It is possible to call the parent method in the child method since the block's content is written in PHP. It is also possible to call parent methods in non-PHP blocks with respect to the language syntax.
To do so, it is mandatory to know the name of the class method generated by each block. Here is a list of blocks with corresponding methods.
# PHP Blocks
Validation            => protected _validation(SuperValidationException $exception)
Predicate             => protected _predicate($input, string &$message)
Marshal<database>     => protected _marshalDatabase($input)
Unmarshal<database>   => protected _unmarshalDatabase($input)
Marshal<php>          => protected _marshal($input)
Unmarshal<php>        => protected _unmarshal($input)
Type<database>          => protected _databaseType()
Type<php>          => protected _phpType()
# non-PHP Blocks
Marshal<FRONT_LANG>   => protected _marshal($input)
Unmarshal<FRONT_LANG> => protected _unmarshal($input)
Type<FRONT_LANG>          => protected _type()
So for example if one needs to call parent predicate in php, they may write parent::predicate($input).
Also, the arguments of child super override the ones in the parent. This override includes type, default value, and being constant. It is also possible to add new arguments to the type. Read Examples for more details.

Visibility

The visibility of supers is controlled via the Marshal and Unmarshal blocks. Although the supers are never accessible in the front-end project, the Marshal library can use these methods to send and receive values of a type. However, if the methods are not available, this means that we do not want the Marshal library to be able to use them.
By the way, there is usually no point in controlling Super's visibility.

Usage

It is possible to use a defined super in both PHP and Xua languages, but not in Marshal library.

Inside PHP

Although it is not usually helpful to work with supers inside PHP codes, it is possible.

Make a type

The following code makes a type by giving arguments to a super.
$type = new Path\To\Super\SuperName([
    'argName0' => $value0,
    ...,
    'argNameN' => $valueN,
]);

Determine a type

Assume a type is given in a variable and we need to know what type is it. We can try to find the class of the object but what about arguments? The parameters method returns the array that was given to the Super at first place, and the toString method returns a string that describes the type.
var_dump(get_class($type));
var_dump($type->parameters());
var_dump($type->toString());
This code dumps the following.
string "Path\To\Super\SuperName"
[
    argName0 => value0,
    ...,
    argNameN => valueN
]
string "Path\To\Super\SuperName(argName0 = value0, ..., argNameN = valueN)"

Accepts, Implicit & Explicit

There are three accepts functions defined on a type.
explicitlyAccepts
This function will return true only if the value explicitly fits in the type. The second argument is optional, and if the return value is false, the function may fill it with a reason.
if ($type->explicitlyAccepts($value, $reason)) {
    var_dump($value);
    echo 'is of type ' . $type->toString();
} else {
    echo 'Rejected, because:' . $reason;
}
implicitlyAaccepts
This function will return true if the value explicitly fits in the type, or fits after unmarshaling. The unmarshaling methods must be determined by the caller. The function tries the value itself first, then tries to unmarshal and check if the value fits by given methods, one by one. If not passed to the function, the default value [self::METHOD<em>IDENTITY, self::METHOD</em>UNMARSHAL, self::METHOD<em>UNMARSHAL</em>DATABASE] is assumed.
if ($type->implicitlyAccepts($value, $reasons, [self::METHOD_IDENTITY, self::METHOD_UNMARSHAL
])) {
    var_dump($value);
    echo 'kinda fits in the type ' . $type->toString();
} else {
    echo 'fully rejected because of the following reasons';
    var_dump($reasons);
}
accepts
This function does the same job of implicitlyAccepts, but may alter the original value while trying to fit it in the type.
$originalValue = $value;
if ($type->accepts($value, $reasons, [self::METHOD_IDENTITY, self::METHOD_UNMARSHAL
])) {
    var_dump($originalValue);
    echo 'was changed to';
    var_dump($value);
    echo 'to fit in type ' . $type->toString();
} else {
    echo 'fully rejected because of the following reasons';
    var_dump($reasons);
}

Marshal & Unmarshal

There are two types of marshal and unmarshal functions available in the server project.
Network transmissions
Functions marshal and unmarshal are responsible to marshal and unmarshal values for purpose of network transmissions.
$marshaledValue = $type->marshal($value);
$originalValue = $type->unmarshal($marshaledValue);
if ($value !== $originalValue) {
    var_dump('something is wrong with marshal/unmarshal functions of ' . $type->toString());
}
Database Storing & Restoring
Functions marshalDatabase and unmarshalDatabase are responsible to marshal and unmarshal values for purpose of storing and restoring into/from database.
$marshaledValue = $type->marshalDatabase($value);
$originalValue = $type->unmarshalDatabase($marshaledValue);
if ($value !== $originalValue) {
    var_dump('something is wrong with marshalDatabase/unmarshalDatabase functions of ' . $type->toString());
}

Native Type

The functions phpType databaseType have no arguments and return a string that declares the type in PHP and database engine syntax. phpType is mostly used in PHPDocs to declare the classes properties type, while databaseType is mostly used to tell the database server how the type values must be stored. These functions are hardly helpful in programming.

Inside Xua

The main usage of Supers is to declare types for Xua Super arguments, Entity fields, and Method request and response signatures (and Method field signatures for VARQUE Methods). To mention a type, one must call a Super and give it arguments. The type then can be used for type declaration. The syntax is the following.
Path\To\Super\SuperName(
    argName0 = constant0,
    ...,
    argNameN = constantN,
)

Range

In this section, we want to work with a Super that accepts the range of integers in $[a, b)$.

Definition

First of all, we need to define the Super. We put it in the file Supers/Integers/Range.xua.
Extension
A value of this type needs to be an integer so we can reuse marshal, unmarshal, and native type methods of the Integer Super, and also the predicate of that Super would be useful to check if the value is an integer. So it seems like a good idea to extend the Super Integer.
Super extends Integer
Arguments
We need to have two arguments determining the start and end of the range. The arguments must be integer themselves.
# Supers\Integers\Range
Super extends Integer {
    arguments {
        start : Integer();
        end   : Integer();
    }
    validation {
        # TODO implement
    }
    predicate {
        # TODO implement
    }
}
Validation
For the validation, we must check that the second number is not less than the first one. Note that we do not need to check if the type of arguments is integer since we already declared their type so the type checking is automatically done.
validation {
    if ($this->end < $this->start) {
        $exception->setError('end', 'The argument `end` cannot be less than the argument `start`.');
    }
}
Predicate
We can just simply check if the $input is an Integer and is in the range.
Predicate {
    return parent::_predicate($input) and $this->start <= $input and $input < $this->end;
}
But we can make it a little more sophisticated by providing a reason of why the value may fail to fit.
Predicate {
    if (!parent::_validation($input, $message)) {
        // The message is already filled here by the parent _validation
        return false;
    }
    if ($input < $this->start) {
        $message = $input . ' is less than ' . $this->start;
        return false;
    }
    if ($input >= $this->end) {
        $message = $input . ' is not less than ' . $this->end;
        return false;
    }
    return true;
}
Note that it is OK to fill the $message when the return value is true. Xua automatically clears the $message in that case.
Marshal and Unmarshal
The defined Super is ready-to-go with no further modifications; since the Integer Super includes the desired Marshal and Unmarshal functions and Range inherits them. However, for the purpose of this documentation, we override them with a new network transmit marshaling system.
The silly idea is to shift starting number to zero to have smaller integers which are easier to transmit (practically useless). For example The range $[1000, 1100)$ can be shifted to $[0, 100)$.
Marshal<php> {
    return $input - $this->start;
}
Unmarshal<php> {
    return $input + $this->start;
}
Marshal<javascript> {
    return input - this.args.start;
}
Unmarshal<javascript> {
    return input + this.args.start;
}
Native Type
The PHP type is inherited and returns int, which is great. However, the database type is an interesting part of the definition since we can determine the length of MySQL INT by the range limits.
We know that INT(n) can store values in range $[-2^{n-1}, 2^{n-1}]$. So it is efficient to find the least $n$ such that this range contains our range. First, we find the maximum absolute value that can fit in the type.
$min = $this->start;
$max = $this->end - 1;
$absMax = max(abs($min), abs($max));
Let us say this number is $M$. we must find $n$ such that $M \leq 2^{n-1}$. $$\begin{eqnarray} M \leq 2^{n-1} & \iff & \log_2(M) \leq n - 1 \\ (\text{Since $n - 1$ is integer}) & \iff & \lceil \log_2(M) \rceil \leq n - 1 \\ & \iff & \lceil \log_2(M) \rceil + 1 \leq n \\ \end{eqnarray}$$
So the minimum value of $n$ is $\lceil \log_2(M) \rceil + 1$.
$n = ceil(log($absMax, 2)) + 1;
This leads us to the following database type.
type<database> {
    $min = $this->start;
    $max = $this->end - 1;
    $absMax = max(abs($min), abs($max));
    $n = ceil(log($absMax, 2)) + 1;
    return "INT($n)";
}
Validation
Assume that we want another Super with the same features, but only for positive values. We can extend again what we already have.
# Supers\Integers\PositiveRange
Super extends Range () {
    validation {
        parent::validation();
        if ($this->start<= 0) {
            $exception->setError('start', 'The range must be positive.');
        }
    }
}
Constant Arguments
Or assume we want a range super that can only start at zero.
# Supers\Integers\NaturalUpperLimit
Super extends Range {
    arguments {
        const start : Integer() = 0;
    }
}
Note the way we overrode the start with default value, but also set it constant so the caller cannot change it.
$type = new NaturalUpperLimit(['start' => 1, 'end' => 2]);
This code will result in an uncaught SuperValidationException.
Marshaling
But better than these, assume we use the network transmit marshaling procedures for the database, which actually impacts the table size. Furthermore, we can remove the silly network transmit marshaling procedure. Also, note that we must override the type<database> as well.
# Supers\Integers\EfficientRange
Super extends Range () {
    Marshal<php> {
        return $input;
    }
    Unmarshal<php> {
        return $input;
    }
    Marshal<javascript> {
        return $input;
    }
    Unmarshal<javascript> {
        return $input;
    }
    Marshal<database> {
        return $input - $this->start;
    }
    Unmarshal<database> {
        return $input + $this->start;
    }
    DatabaseType {
        $max = $this->end - $this->start - 1;
        $n = ceil(log($max, 2)) + 1;
        return "INT($n)";
    }
}
New Arguments
Another example is when we want to add arguments to a Super. For example, assume we want a step argument. By default, the step is one, but if we set the step to three, the type accepts start and every third number.
# Supers\Integers\StepRange
Super extends Range {
    arguments {
        step : Integer() = 1;
    }
    validation {
        parent::validation();
        if (step < 1) {
            $exception->setError('step', 'The step must be at least 1.');
        }
    }
    predicate {
        if (!parent::predicate($input, $message)) {
            return false;
        }
        if ($input - $this->argument['start'] % $this->argument['step'] != 0) {
            $message = "value {$input} minus starting point {$this->argument['start']} is not devisable by step value {$this->argument['step']}.";
            return false;
        }
        return true;
    }
}

Usage

After defining a super, we can use it to define types and use them.
Inside PHP
First, Let us define a type that accepts the range $[10, 30)$. We use the EfficientRange.
$type = new EfficientRange(['start' => 10, 'end' => 30]);
Let us see how Xua stringifies this type.
var_dump($type->toString());
This code dumps Supers\Integers\EfficientRange(start = 10, end = 30).
The value 25 explicitly fits in the type, while the value 5 implicitly fits.
$value = 25;
var_dump($type->explicitlyAccepts($value)); # dumps true
$value = 5;
var_dump($type->implicitlyAccepts($value)); # dumps true
Of course the value 25 also fits in the type implicitly. This is because the function first checks if the value explicitly fits.
$value = 25;
var_dump($type->implicitlyAccepts($value)); # dumps true
We know that value 15 is ambiguous. It can be interpreted as 15 itself, a value in range, or the result of marshaling 25. Let us call accept and check the result.
$value = 15;
var_dump($type->accepts($value)); # dumps true
var_dump($value); # dumps 15
Since the function first tries the explicit, if the value fits explicitly, function does not change the value. What about 5 that can only fit implicitly?
$value = 5;
var_dump($type->accepts($value)); # dumps true
var_dump($value); # dumps 15
This time the function tries to fit the value explicitly and fails, so it goes for unamrshaling, which leads to accept.
We know the value $5$ does not fit explicitly, but we may wonder why. (It is super obvious but is a good way to see how to get the reason from the function.)
$value = 5;
var_dump($type->explicitlyAccepts($value, $reason)); # dumps false
var_dump($reason); # dumps '5 is less than 10'
What about value 30?
$value = 30;
var_dump($type->explicitlyAccepts($value, $reason)); # dumps false
var_dump($reason); # dumps '30 is not less than 30'
But we know 30 does not even implicitly fit.
$value = 30;
var_dump($type->accepts($value, $reasons)); # dumps false
var_dump($reasons);
This function fills the $reasons with an array, reasoning about each failure.
[
    'identity' => '30 is not less than 30',
    'unmarshal' => '30 is not less than 30',
    'unmarshalDatabase' => '40 is not less than 30',
]
The $reason variable can work as a log; for example, we try the code above with 5 and get the following.
[
    'identity' => '5 is less than 10',
    'unmarshal' => '5 is less than 10',
    'unmarshalDatabase' => null,
]
We can see there is no reason for the unmarshalDatabase, and it makes sense since the value fits using this unmarshal method.
We know that the storing in the database is done by marshaling. The marshaled values are in $[0, 20)$.
var_dump($type->DatabaseType()); # Dumps 'INT(6)'
In this case, there is no efficiency since the original values would only need 6 bits too. Although we could make this better if we shifted the center of the range to zero instead of starting point. That way, we would have the range $[-10, 10)$ which needs only $5$ bits to store. By the way, none of this is useful because MySQL occupies at least a byte which is 8 bits, and there is no difference between 5 and 6 in practice. Although this marshaling method may come in handy for big values, Xua's official Range does not use it because of the ambiguity.
Inside Xua
We may use a Super to define another Super.
# Supers\Gender
Super {
    arguments {
        possibilities : Range(start = 2, end = 4) = 2;
    }
    predicate {
        $choices = ['male', 'female'];
        if ($this->possibilities == 3) {
            $choices = ['male', 'female', 'non-binary'];
        }
        $message = '$input is not in ' . implode(", ", $choices);
        return in_array($input, $choices);
    }
}
Note how we set the $message without caring about the return value. Xua automatically clears the $message if the return value is true.

Good News

Although defining a simple Super seems easy, defining a complete Super with all features in many languages seems exhausting. The good news is, Xua provides a set of predefined supers that cover almost any need. We discuss them in the next chapter.