Supers; A Whole Level Beyond Types
Effects on the projects
Each super results in a PHP class in the Server project, extending the abstract classSuper
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, calledUniversal
.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
Thevalidation
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
ThePredicate
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
BlocksMarshal
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 theType
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? Theparameters
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 threeaccepts
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 ofimplicitlyAccepts
, 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
Functionsmarshal
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
FunctionsmarshalDatabase
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 functionsphpType
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 fileSupers/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 theInteger
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 theInteger
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 returnsint
, 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 thetype<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 astep
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 theEfficientRange
.$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
.
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.