Types¶
Types describe the values that a declaration can accept. Safe-DS has various categories of types, which are explained in this document.
Categories of Types¶
Named Types¶
Named types either denote that a declaration must be an instance of a class or one of its subclasses, or an instance of a variant of an enum. In either case the syntax of the type is just the name of the class or the enum respectively.
Class Types¶
A declaration with a class type must be an instance of a class or one of its subclasses. Let us use the following classes for our example:
To denote that a declaration accepts instances of SomeClass
and its subclass SomeSubclass
, we write the name of the class as the type:
Nullable Class Types¶
The value null
(see null) deserves special treatment since it is not possible to operate on it in the same manner as on proper instances of a class. For this reason null
cannot be assigned to declarations with class types such as SomeClass
.
To specifically allow null
as a value, simply add a question mark to the named type:
Enum Types¶
A declaration with an enum type must be one of the variants of the enum. Let us use the following enum for our example:
To denote that a declaration accepts instances of any variant of SomeEnum
, use the name of the enum as the type:
This type expects either the value SomeEnum.SomeEnumVariant
(see member access) or anything constructed from the variant SomeOtherEnumVariant
such as SomeEnum.SomeOtherEnumVariant(3)
.
Type Arguments¶
Note: This is an advanced section. Feel free to skip it initially.
If a declaration has type parameters we need to assign all of them when we use the declaration as a named type. This assignment happens in the form of type arguments. We explain this using the following declaration:
When we use this class as a named type, we need to specify the value for the type parameter T
, which is supposed to denote the type of the elements in the list.Similar to calls, we can either use positional type arguments or named type arguments.
In the case of positional type arguments, they are mapped to type parameters by position, i.e. the first type argument is assigned to the first type parameter, the second type argument is assigned to the second type parameter and so forth.
If a positional type argument is used, we just write down its value, which is a type projection.
For example, if we expect a list of integers, we could use the following type:
Let us break down the syntax:
- The usual named type (here
SomeSpecialList
). - Opening angle bracket.
- A positional type argument (here
Int
). - A closing angle bracket.
When a named type argument is used, we explicitly specify the type parameter that we want to assign. This allows us to specify them in any order. It can also improve the clarity of the code since the meaning of the type argument becomes more apparent. Here is the type for our list of integers when a named argument is used:
These are the syntactic elements:
- The usual named type (here
SomeSpecialList
). - Opening angle bracket.
- A named type argument (here
T = Int
). This in turn consists of - The name of the type parameter (here
T
) - An equals sign.
- The value of the type argument, which is still a type projection.
- A closing angle bracket.
Within a list of type arguments both positional and named type arguments can be used. However, after the first named type arguments all type arguments must be named.
Let us finally look at how multiple type arguments are passed. For this we use the following declaration:
This class has to type parameters, namely K
and V
, which must both be set if we use this class as a named type.
Here is a valid use:
We will again go over the syntax:
- The usual named type (here
SomeSpecialMap
). - An opening angle bracket.
- The list of type arguments. Each element is either a positional or a named type argument (see above). Individual elements are separated by commas. A trailing comma is allowed
- A closing angle bracket.
We will now look at the values that we can pass within type arguments.
Type Projection¶
The most basic case is that we pass a concrete type as the value. We have already seen this in the example above where we constructed the type for a list of integers:
The value of the type argument is just another named type (here Int
).
Member Types¶
A member type is essentially the same as a named type with the difference that the declaration we refer to is nested inside classes or enums.
Class Member Types¶
We begin with nested classes and use these declarations to illustrate the concept:
To specify that a declaration accepts instances of SomeInnerClass
or its subclasses, use the following member type:
This has the following syntactic elements:
- Name of the outer class (here
SomeOuterClass
). - A dot.
- Name of the inner class (here
SomeInnerClass
).
Classes can be nested multiple levels deep. In this case, use a member access for each level. Let us use the following declarations to explain this:
To specify that a declaration accepts instances of SomeInnerClass
, or its subclasses, use the following member type:
If any referenced class has type parameters these must be specified by type arguments. For this we use these declarations:
To specify that a declaration accepts instances of SomeInnerClass
where all type parameters are set to Int
, or its subclasses, use the following member type:
Finally, as with named types, null
is not an allowed value by default. To allow it, add a question mark at the end of the member type. This can be used independently of type arguments:
Enum Variant Types¶
Member types are also used to specify that a declaration is an instance of a single variant of an enum. For this, we use the following declarations:
To allow only instances of the variant SomeEnumVariant
, use the following member type:
Let us take apart the syntax:
- The name of the enum (here
SomeEnum
). - A dot.
- The name of the enum variant (here
SomeEnumVariant
).
Identical to class member types, all type parameters of the enum variant must be assigned by type arguments. We use these declarations to explain the concept:
To now allow only instances of the variant SomeEnumVariant
with Int
values, use the following member type:
Union Types¶
If a declaration can have one of multiple types you can denote that with a union type:
Here is a breakdown of the syntax:
- The keyword
union
. - An opening angle bracket.
- A list of types, which are separated by commas. A trailing comma is allowed.
- A closing angle bracket
Note that it is preferable to write the common superclass if this is equivalent to the union type. For example, Number
has the two subclasses Int
and Float
. Therefore, it is usually better to write Number
as the type rather than union<Int, Float>
. Use the union type only when you are not able to handle the later addition of further subclasses of Number
other than Int
or Float
.
Callable Types¶
A callable type denotes that only values that can be called are accepted. This includes:
Additionally, a callable types specifies the names and types of parameters and results. Here is the most basic callable type that expects neither parameters nor results:
Let us break down the syntax:
- A pair of parentheses, which is the list of expected parameters.
- An arrow
->
. - A pair of parentheses, which is the list of expected results.
We can now add some expected parameters:
These are the syntactic elements:
- Parameters are written in the first pair of parentheses.
- For each parameter, specify:
- Its name.
- A colon.
- Its type.
- Separate parameters by commas. A trailing comma is permitted.
Finally, we can add some expected results:
The syntax is reminiscent of the notation for parameters:
- Results are written in the second pair of parentheses.
- For each result, specify:
- Its name.
- A colon.
- Its type.
- Separate result by commas. A trailing comma is permitted.
If exactly one result is expected, the surrounding parentheses may be also removed:
Unknown¶
If the actual type of a declaration is not known, you can denote that with the special type unknown
. However, to later use the declaration in any meaningful way, you will have to cast it to another type.
Corresponding Python Code¶
Note: This section is only relevant if you are interested in the stub language.
Optionally, type hints can be used in Python to denote the type of a declaration. This is generally advisable, since IDEs can use this information to offer additional feature, like improved refactorings. Moreover, static type checker like mypy can detect misuse of an API without running the code. We will now briefly describe how to best use Python's type hints and explain how they relate to Safe-DS types.
First, to get type hints in Python closer to the expected behavior, add the following import to your Python file:
Also add the following import, which brings the declarations that are used by the type hints into scope. You can remove any declaration you do not need:
The following table shows how Safe-DS types can be written as Python type hints:
Safe-DS Type | Python Type Hint |
---|---|
Boolean |
bool |
Float |
float |
Int |
int |
String |
str |
SomeClass |
SomeClass |
SomeEnum |
SomeEnum |
SomeClass? |
Optional[SomeClass] |
SomeEnum? |
Optional[SomeEnum] |
SomeSpecialList<Int> |
SomeSpecialList[int] |
SomeOuterClass.SomeInnerClass |
SomeOuterClass.SomeInnerClass |
SomeEnum.SomeEnumVariant |
SomeEnum.SomeEnumVariant |
union<String, Int> |
Union[str, int] |
(a: Int, b: Int) -> r: Int |
Callable[[int, int], int] |
(a: Int, b: Int) -> (r: Int, s: Int) |
Callable[[int, int], Tuple[int, int]] |
Most of these are rather self-explanatory. We will, however, cover the translation of callable types in a little more detail: In Python, the type hint for a callable type has the following general syntax:
To get the <list of parameter types>
, simply
- convert the types of the parameters to their Python syntax,
- separate them all by commas,
- surround them by square brackets.
Getting the <result type
> depends on the number of results. If there is only a single result, simply write down its type. If there are multiple types, do this instead:
- convert the types of the results to their Python syntax,
- separate them all by commas,
- add the prefix
Tuple[
, - add the suffix
]
.