We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.

PhantomPL

From Esolang
Jump to navigation Jump to search

PhantomPL is designed by PSTF.

Overview

PhantomPL, as a practical programming language, is mostly used for data science and backend development.

Because it is used for data science, it is mostly inspired from Python.

Volume 1: Basic Syntax

Chapter 0: Our First Program

#> Hello.ptpl
print("Hello, World!")

Chapter 1: Program Structure

PhantomPL programs don't need an explicit entry point like C or Rust. Unless a main function is defined, the program usually starts at the first line. The interpreter should scan over the program and check if there is a main function. If so, run the program from the main function, otherwise run from the first line.

Chapter 2: Data Types and Structures

Section 1: Data Types

PhantomPL supports the following types of data:

  1. Integer. A sequence of Indo-Arabic numerals is called an integer. PhantomPL has no limits on integer precision, so you can create integers of any size. Some examples of integers are 3, 15, 114514, -28, and so on.
  2. Decimals. A decimal is just a sequence of two sets of numbers separated by a decimal point. Similarly, PhantomPL doesn't have precision limits for decimals, so you won't run into situations like 0.1 plus 0.2 equaling 0.30000000000000004. To distinguish between finite and infinite decimals, PhantomPL lets you add three dots after a decimal to indicate it's infinite (accurate up to the part before the three dots). If there are at least three repeating cycles before the three dots, PhantomPL will treat it as a rational number.
  3. String. A string is a sequence of characters enclosed in double or single quotes. You can think of it as a list, and it's an indexable data structure. It supports some escape sequences. Other types of strings include f-strings (formatted strings) and r-strings (raw strings or regular expressions), as well as multi-line strings (enclosed in triple or six quotes).
  4. Boolean value. A value that is either true or false. Everything empty can be considered false, and all other values can be considered true. 0 is actually a kind of empty value. Empty strings, empty lists, and so on are all empty values.
  5. List. PhantomPL's lists are similar to Python's lists and Polynomix's arrays, and they can store any type, any value, and any number of data.
  6. Pair. A pair of value with same or different types. Pairs can also contain pairs.
  7. Void. It is a common subclass of all types. It has only one value, void, which is also its type symbol. When it is converted to a value of another type, it is converted to the default value of the target type, for example, 0.
  8. Key-value pairs. Similar to ordered pairs, key-value pairs also consist of two elements, but the first element must be a string, and the separator is a colon instead of a comma. For convenience, we call a list where each element is a key-value pair a dictionary.
  9. Automatic type (Everything). All types are its subclasses. Depending on the value, it can automatically guess and convert to the target data type.
  10. Function. A function reference, sometimes also called a subroutine or lambda.
  11. Pointer. A pointer is an address reference, and its size depends on your memory size.
  12. Type. A type itself is also a data type. It can't be converted to or from anything.

Section 2: Variable Declaration

let [modifier] variable_name [(variable_type)] <- variable_value;

This statement declares a variable. A variable's identifier has to follow these rules: it must start with an XID_start and be followed by zero, one, or more XID_continue characters. Under Unicode normalization form C (NFC), identifiers that look the same are treated as identical, for example, the Kelvin sign and the Latin capital letter K are considered the same character. Identifiers that match keywords aren't valid normal identifiers, but they are valid 'raw identifiers.' Raw identifiers start with $ and end with |$. PhantomPL uses Unicode standard version 17.0.0.

If you don't understand XID_start and XID_continue, here's a more beginner-friendly way to put it:

  • It has to be made up of characters that aren't special symbols, including uppercase letters, lowercase letters, standard numbers, underscores, and some characters from other languages.
  • The first character can't be a number.
  • Don't use a name that's the same as a keyword.
  • Try to make two identifiers look different, or else others—or even the compiler or interpreter—might think they're the same identifier. For example, try to avoid using symbols that look similar, like 0x212A (K) and 0x004B (K).
  • Although it's not a strict requirement, try to keep identifiers simple and clear. If possible, avoid overly simple single-letter identifiers and overly complicated or ritually overly proper long identifiers. Programming isn't feudal.

Besides, you can not assign to "_" because it is reserved as a wildcard variable.

Section 3: Modifier

The available modifiers include mutability modifiers, visibility modifiers, and static/dynamic modifiers.

Mutability modifiers include var (mutable) and const (immutable), meaning variable and constant, respectively. By default, variables are mutable (var).

Visibility modifiers include public, global, local, and private, which will be explained in detail in the "Variable Visibility" chapter. By default, a variable's visibility is global, which you can simply think of as "visible to the current program" for now.

Static/dynamic modifiers mainly include static (other static/dynamic modifiers aren't used in this tutorial, but they are also part of PhantomPL). These modifiers affect how class members are stored and referenced.

Chapter 3: Expressions and Codes

Section1: Expressions

Any expression that can be evaluated is a valid PhantomPL expression. Expressions are divided into value expressions and code.

let voltage <- 5.0;
let bit (bool) <- (voltage > 2.5)?true:false;

In these two expressions, we define the voltage of a certain bit as 5 volts, and then we define the bit's state based on the rule that if the voltage is above 2.5 volts, it's 1, otherwise it's 0.

print("Hello, World!");

This expression doesn't return anything, but it will print "Hello, World!" on the screen.

let number <- 1;
for _ in interval(1, 100, 1, true, true):
    number <- number * _;
rof

In the end, the number should equal 100!, which is 93 326 215 443 944 152 681 699 238 856 266 700 490 715 968 264 381 621 468 592 963 895 217 599 993 229 915 608 941 463 976 156 518 286 253 697 920 827 223 758 251 185 210 916 864 000 000 000 000 000 000 000 000(grouped by spaces), a huge 158-digit number.

Common Used Operator Sheet
Operator Name Description
- Negative For integers or decimals, take their opposite. For lists or strings, reverse them. For pairs of numbers, swap the first and second values. For key-value binds, negate the value.
not Logical NOT Evaluates into false if the original expression was true.
! Named parameter indicator Indicates that the parameter is named.
+ Addition If both operands are numbers, add them together. If both operands are lists or strings, append the second to the first.
- Subtraction Like what in mathematics.
* Multiplication If both operands are numbers, return their product. If one is a string or list and the other is a natural number n, repeat it n times and return it.
/ Division Like what in Python.
// Truncated Division Like what in Python.
% Modulo Like what in Python.
^ Exponent Like what in Xonovile.
^^ Double exponent Not really common, but anyway, x^^y returns where there are y layers of x.
& Bitwise AND Literally meaning.
| Bitwise OR Literally meaning.
or Logical OR Returns true if one of the expression is true.
and Logical AND Returns true onlt if both expression is true.
~ Bitwise NOT Literally meaning.
@ Bitwise XOR Literally meaning.
# Neglection Inline comment.
<< Left Shift Multiply x by 2y.
>> Right Shift Divide x by 2y. If it's used on integers, round the result toward 0.
<- Assignment Assign y to x.
?$ XXXXX $? Document Comment Literally meaning.
[}$ XXXXX ${] Markdown-style Comment Literally meaning.
do XXXXX end Code block delimiter Literally meaning.
= Equality Returns true if x is equal to y.
!= Unequality Returns false if x is equal to y.
> Superior Returns true if x is greater than y.
<= Non-Superior Returns false if x is greater than y.
< Inferior Returns true if x is less than to y.
>= Non-inferior Returns false if x is less than y.
b?a:c Conditional Evaluates into a if b is true else c.

Section 2: Control Flows

Conditional-branch

The format of a basic conditional statement is shown as follows.

if (condition) expression1 else expression2 endif

It means that if the condition is true, expression 1 is evaluated; otherwise, expression 2 is evaluated. If you want to do nothing when the condition isn't true, you can omit the part after else. Of course, although it's not a very useful feature, if you want expression 2 to be evaluated when the condition is true, you can replace if with ifn.

Of course, this expression supports more branches, which can be done by adding else if (condition) blocks.

Conditional Loop

The format of a check-before-do loop statement is shown as follows.

while (condition) expression endwhile

It works according to the following mechanism:

  1. Evaluate the condition expression. If it's true, go to step 2; otherwise, exit the loop.
  2. Execute the loop body. If a break is encountered, exit the loop. If a continue is encountered, skip the remaining code and go back to step 1. If neither is encountered, after finishing the loop body, go back to step 1.

There is also a check-after-do loop statement:

repeat expression while (condition);

Its operating mechanism is as follows:

  1. Execute the loop body. If you encounter a break, exit the loop. If you encounter a continue, start over and execute again. If nothing is encountered, after finishing the loop body, go to step 2.
  2. Check if the condition still holds. If it does, go back to step 1; otherwise, exit the loop.

Although it's also a not very useful feature, if you want the loop body to stop executing when a condition is met, you can change 'while' to 'until'.

Iterative Loop

for (iterator in sequence where condition) expression endfor

This is the basic structure of an iterative loop. Its operation works like this:

  1. Generate an iterable container based on the sequence, and point the iteration pointer at the container.
  2. Move the iteration pointer forward by one element. If all elements in the iterable container have been iterated over (the pointer is after the last element of the container), exit the loop. Otherwise, check if the condition in the 'where' clause is met. If it is, go to step 3; if not, repeat step 2.
  3. Bind the iterator to the iteration pointer and transfer the value, then execute the loop body. If 'break' is encountered, exit the loop. If 'continue' is encountered, go straight to step 2. If nothing is encountered, after executing the loop body, go to step 2 again.

Use iterative loop in iterable items

You can put certain regular elements into a list through iterative loops. For example, the expression below will generate the squares of numbers from 1 to 100.

[x ^ 2 for x in interval(1, 100, 1, true, true) endfor];

Chapter 3: Functions

A function is a combination of some statements. It can encapsulate certain specific functionalities so that each time you want to perform that function, you don't have to make all sorts of changes or copy and paste.

Section 1: Definition

The format for defining a function is shown below.

func FunctionName (args: types, kwargs!: types) returning type do
    # Function body
    return something;
end

If a function's return type is void, you can skip the return statement in the function.

For example, this function will return "Fizz" when detected 3x, "Buzz" when detected 5x, "FizzBuzz" when detected 15x and the argument as string when detected an integer that is neither divisible by 3 nor by 5.

# fizzbuzz.ppl
func fizzbuzz (x: Int) returning String do
    return (x%3=0)?((x%5=0)?"FizzBuzz":"Fizz"):((x%5=0)?"Buzz":cast(x, String));
end
for i in interval(1, 100, 1, true, true) do
    print(fizzbuzz(i));
end

And this is a function which greets users with simple sentence.

# greeting.ppl
func greet (user!: Str) returning Void do
    print("Hello, %s!" % [user]);
end

greet("Stephen");

Section 2: Calling a Function

As shown above, to cast a function, you write this:

functionName(*args, **kwargs);

Section 3: Recursion and Override

A function can also call other functions inside itself, or even call itself. Calling the function itself from within the function is called recursion. It can usually make the code more intuitive, but it will take up some time and memory.

Programs also allow two functions with the same name but different functions; this is called function overloading.

For example, these are two fibonacci functions for integers and decimals:

include "math".*
let const phi <- (1 + sqrt(5)) / 2;
let const psi <- (1 - sqrt(5)) / 2;
func fibonacci (x: Int) returning Int do
    if x <= 1 do
        return 1;
    else do
        return fibonacci(x - 2) + fibonacci(x - 1);
    endif
end
func fibonacci (x: Float) returning Float do
    return (1 / sqrt(5)) * (phi ^ x - psi ^ x);
end

Among them, the Fibonacci function for integers uses recursion, while the one for floating-point numbers uses the closed-form formula , where phi = and psi = .

The following code snippet defines a foo function that returns a corresponding greeting message if it receives a string, and if it receives a number, it plugs it into the general formula for the Fibonacci sequence and returns the result.

include "math".*
let const phi <- (1 + sqrt(5)) / 2;
let const psi <- (1 - sqrt(5)) / 2;
func foo (x: Number) returning Float do
    return (1 / sqrt(5)) * (phi ^ x - psi ^ x); # Both Int and Float are subclasses of Number.
end
func foo (x: Str) returning Str do
    return "Hello, %s!" % x;
end

Operator overload

If you want to support an operator that a type does not natively support, you can implement it using operator overloading.

If you need to overload an operator for a type, you can do so by defining a function with the same name as the operator for that type. When an instance of that type uses the operator, the operator function will be called automatically.

The definition of an operator function is similar to that of a regular function, with the following differences:

  • When defining an operator function, the operator modifier must be added before the func keyword;
  • The number of parameters of the operator function must match the requirements of the corresponding operator (see the appendix on operators for details);
  • Operator functions can only be defined inside classes, interfaces, structs, enums, and extensions;
  • Operator functions have the semantics of instance member functions, so the static modifier is prohibited;
  • Operator functions cannot be generic functions.

Additionally, it should be noted that overloading an operator does not change its inherent precedence or associativity.

Section 4: Subroutines

A subroutine is a special type of function that doesn't take any parameters and doesn't return any value (or only returns an exit code). The optional main function we mentioned earlier is a typical example of a subroutine.

For example, this function outputs the FizzBuzz sequence from 1 to 100.

# fizzbuzz.ppl
func fizzbuzz (x: Int) returning String do
    return (x%3=0)?((x%5=0)?"FizzBuzz":"Fizz"):((x%5=0)?"Buzz":cast(x, String));
end
func work (Void) returning Void do
    for i in interval(1, 100, 1, true, true) do
        print(fizzbuzz(i));
    end
end

Chapter 4: Lambda Abstractions

Section 1: Definition and Usage

A lambda abstraction is basically an anonymous function.

This type of function is designed to define a feature more quickly in a program without having to give it a name (of course, if you insist on assigning a lambda expression to a variable, that's on you). Here's the definition format for a lambda expression:

{?^ [params: Types, kwparams!: Types] .> {function_body} (real_arguments)}

Here, the part in the bracket parameter list, with multiple parameters separated by commas, and each parameter's name and type separated by a colon. There can also be no parameters inside brackets. The part after .> in the brace is the body of the lambda expression, consisting of a sequence of expressions or declarations. The scope of the lambda expression's parameters is the same as that of a function, and within the body of the lambda expression, its scope level can be considered equivalent to variables defined inside a function body.

Whether a lambda expression has parameters or not, the parameter list and .> cannot be omitted, unless it is used as a trailing lambda.

The type annotation of parameters in a lambda expression can be omitted. In the following situations, if the parameter type is omitted, the compiler will try to infer the type. A compilation error will occur if the compiler cannot infer the type:

  • When a lambda expression is assigned to a variable, the parameter type is inferred based on the type of the variable;
  • When a lambda expression is used as an argument in a function call, the parameter type is inferred based on the type of the corresponding function parameter.

Section 2: Closure

A function or lambda that captures a variable from the static scope in which it is defined is called a closure, together with the captured variable. This allows the closure to function correctly even when it is used outside the scope where it was defined.

Accessing the following types of variables in the definition of a function or lambda is called variable capture:

  1. Accessing a local variable defined outside the function in the default value of a function parameter;
  2. Accessing a local variable defined outside the function or lambda within the function or lambda;
  3. A function or lambda defined inside a class/struct that is not a member function accessing instance member variables or this.

The following types of variable access are not considered variable capture:

  1. Accessing a local variable defined within the function or lambda itself;
  2. Accessing the function or lambda parameters;
  3. Accessing global variables and static member variables;
  4. Accessing instance member variables within instance member functions or properties. Since instance member functions or properties receive this as a parameter, all instance member variables are accessed through this.

Variable capture occurs at the time the closure is defined, so there are the following rules for variable capture:

  1. The captured variable must be visible at the time the closure is defined, otherwise a compile error occurs;
  2. The captured variable must be fully initialized at the time the closure is defined, otherwise a compile error occurs.

To prevent closures that capture variables that have already defined by "let" from escaping, such closures can only be invoked and cannot be used as first-class citizens. This includes not being able to be assigned, in argument list, returned, or expressionized.

Section 3: Trailing lambdas which borrowed from CangjieLang and Gemini

Trailing lambdas can make function calls look like built-in language syntax, increasing the extensibility of the language.

When the last parameter of a function is of function type, and the argument provided for the function call is a lambda, you can use trailing lambda syntax to place the lambda at the end of the function call, outside the parentheses.

For example, in the code below, a myIf function is defined, where the first parameter is of type Bool and the second parameter is a function type. When the first parameter is true, it returns the result of calling the second parameter; otherwise, it returns 0. When calling myIf, you can either call it like a regular function or use the trailing lambda style.

func myIf (something: Bool, fn: lambda .> (Void)) returning Void do
    if something do
        fn();
    else do
        nop;
    endif
end

func test (Void) returning Void do
    myIf(1+1=2, {?.>{print("Hello, World!");}});
    
    myIf(1+1=2) do
        print("Hello, World!")
    end
end

Chapter 5: User-defined Data Types

In addition to built-in data types, users can also create their own data types. These custom data types are called structures. The structures mentioned here are a concept found in most languages.

Section 1: Definition of Structures

Structures are defined by structure keyword.

structure YourStructName inside
    let attrib (Type) [<- value]; # Attribution definition
    
    public func __init__ (initialization_arguments) returning Void do
        # Initialization
    end
    public func method (args: Types, kwargs!: Types) returning Type do
        # Methods
    end
end

This is the syntax of defining a structure. For example, this is a structure of Ellipsis:

include "math".*
structure Ellipsis inside
    let major (Float);
    let minor (Float);
    
    public func __init__ (a: Float, b: Float) returning Void do
        this.major <- a;
        this.minor <- b;
    end
    public func area (Void) returning Float do
        return pi * a * b
        # The formula for the area of an ellipse is S = πab,
        # where a and b are the lengths of the ellipse's semi-major
        # and semi-minor axes, respectively.
    end
end

Struct member variables are divided into instance member variables and static member variables (modified with the static keyword). The difference in access is that instance member variables can only be accessed through a struct instance (saying that a is an instance of type T means that a is a value of type T), while static member variables can only be accessed through the struct type name.

When defining instance member variables, you may omit an initial value (but the type must be specified, like width and height in the example above), or you can provide an initial value.

Section 2: Instances of Structures

After defining a struct type, you can create an instance of the struct by calling its constructor. Outside of the struct definition, you can create an instance of this type by calling the constructor with the struct type name, and you can access instance member variables and instance member functions that meet the visibility modifiers (such as public) through the instance. If you want to modify the values of member variables through a struct instance, you need to define the struct variable as mutable, and the member variables to be modified must also be mutable. When assigning or passing as a parameter, the struct instance will be copied (if the member variable is a reference type, only the reference is copied, not the object it refers to), creating a new instance, and modifications to one instance will not affect the other instance.

A struct supports defining a static initializer, and in the static initializer, static member variables can be initialized through assignment expressions.

The static initializer begins with the keyword combination static func __init__, followed by an empty parameter list and a function body, and it cannot be modified by access modifiers. All uninitialized static member variables must be initialized in the function body, otherwise a compilation error will occur.

Structs support two types of constructors: regular constructors and primary constructors.

A regular constructor begins with the keyword init, followed by a parameter list and a function body. In the function body, all uninitialized instance member variables must be initialized (if the parameter name and member variable name cannot be distinguished, you can use "this" before the member variable to differentiate; "this" refers to the current instance of the struct), otherwise, a compilation error will occur.

In addition to defining multiple regular constructors named init, a struct can also define (at most) one primary constructor. The primary constructor has the same name as the struct type, and the parameters can be in two forms: regular parameters and member variable parameters (you need to "define the parameters as variables"). Member variable parameters simultaneously serve to define member variables and as constructor parameters.

If a struct definition does not have any custom constructors (including primary constructors), and all instance member variables have initial values, a parameterless constructor will be automatically generated (calling this constructor will create an object where all instance member variables have values equal to their initial values); otherwise, this parameterless constructor will not be automatically generated.

Struct member functions are divided into instance member functions and static member functions (modified with the static keyword). The difference between the two is that instance member functions can only be accessed through a struct instance, whereas static member functions can only be accessed through the struct type name. Static member functions cannot access instance member variables or call instance member functions, but instance member functions can access static member variables and static member functions.

Members of a struct (including member variables, member properties, constructors, member functions, and operator functions) are modified with four access modifiers: private, internal, protected, and public, with the default modifier being internal.

  • private means visible within the struct definition.
  • local means visible only within the current package and its subpackages (including subpackages of subpackages).
  • global means visible within the current module (program).
  • public means visible both inside and outside the module.

Structs defined recursively or mutually recursively are both illegal.

Chapter 6: Structures VS Classes

Of course, besides structs, you can also define a custom data type using a class. The powerful thing about classes compared to structs is that they support inheritance and polymorphism, and they are reference types.

Section 1: Terminology

Class
Used to describe a collection of objects that have the same attributes and methods. It defines the attributes and methods shared by each object in the collection. An object is an instance of a class.
Method
Functions defined within a class.
Class variable
Class variables are shared among all instantiated objects. Class variables are defined inside the class but outside of any function body. ; Class variables are usually not used as instance variables.
Data member
Class variables or instance variables used to handle data related to the class and its instances.
Method overriding
If a method inherited from a parent class does not meet the needs of the subclass, it can be modified. This process is called method overriding, also known as method overwrite.
Local variable
Variables defined within a method, only applicable to the current instance of the class.
Instance variable
In a class declaration, properties represented by variables are called instance variables. An instance variable is a variable prefixed with self.
Inheritance
When a derived class inherits the fields and methods of a base class. Inheritance also allows an object of a derived class to be treated as an object of the base class. For example, with a design where a Dog object is derived from the Animal class, this simulates an "is-a" relationship (for example, Dog is an Animal).
Instantiation
Creating an instance of a class, a concrete object of the class.
Object
An instance of a data structure defined by a class. An object includes two types of data members (class variables and instance variables) and methods.

Section 2: Definition

The definition of a class is quite similar to that of a struct — a name, some properties, a constructor, and some other methods. For example, the following code rewrites the ellipse struct defined in the previous chapter as a class:

include "math".*
class Ellipsis {
    let major (Float);
    let minor (Float);
    
    public static func __init__ (a: Float, b: Float) returning Void do
        this.major <- a;
        this.minor <- b;
    end
    public func area (Void) returning Float do
        return pi * this.major * this.minor;
        # The formula for the area of an ellipse is S = πab,
        # where a and b are the lengths of the ellipse's semi-major
        # and semi-minor axes, respectively.
    end
}

See? Very similar. It's not because I'm lazy, it's just that structures really do have things in common with classes.

A class modified with abstract is an abstract class. Unlike a regular class, in an abstract class, you can define regular functions as well as declare abstract functions (without a function body). The open modifier is optional when defining an abstract class, and you can also use the sealed modifier to declare an abstract class, indicating that it can only be inherited within the same package.

After defining a class type, you can create an object by calling its constructor (using the class type name to call the constructor). Once an object is created, you can access its instance member variables and instance member functions (that are declared public) through the object. If you want to modify the values of member variables through the object (which is not recommended; it's better to modify them through member functions), you need to define the member variables in the class as mutable (i.e., defined with no mutable-modifier or with var). Unlike structures, when objects are assigned or passed as parameters, the object is not copied. Multiple variables point to the same object, so modifying a member variable through one variable will also change the corresponding member variable in the other variables.

Section 3: Inheriting and Polymorphism

include "math".*
class Ellipsis {
    let major (Float);
    let minor (Float);
    
    public static func __init__ (a: Float, b: Float) returning Void do
        this.major <- a;
        this.minor <- b;
    end
    public func area (Void) returning Float do
        return pi * this.major * this.minor;
        # The formula for the area of an ellipse is S = πab,
        # where a and b are the lengths of the ellipse's semi-major
        # and semi-minor axes, respectively.
    end
}
class Circle inheriting Ellipsis {
    let radius (Float);
   
    public override static func __init__ (r: Float) returning Void do
        this.radius <- r;
    end
    
    public override func area (Void) returning Float do
        return pi * this.radius ^ 2;
    end
}

In the code above, we defined an Ellipse class, which has a semi-major axis and a semi-minor axis, and the area formula is S = πab. Then we defined a Circle class through inheritance. Since it's a special type of ellipse, we let it inherit the ellipse's methods, but because of its uniqueness, we also overloaded two of its methods. First, we combined the semi-major axis and semi-minor axis into a radius, so the __init__ function only needs to initialize the radius. Second, when the two factors are equal, their product equals the square of one of the factors, so we overloaded the area function to return πr² (the formula for the area of a circle).

Section 4: Interfaces

An interface is used to define an abstract type. It does not contain data but can define the behavior of a type. A type that declares it implements an interface and implements all the members of that interface is said to have implemented the interface.

Members of an interface can include:

  • Member functions
  • Operator overload functions
  • Member properties

These members are all abstract, requiring the implementing type to have corresponding member implementations.

A simple interface is defined as follows:

interface I { # 'open' modifier is optional.
    func f(): Unit
}

Interfaces are declared using the keyword 'interface', followed by the interface identifier I and the interface members. Interface members can be modified with the 'open' keyword, and the 'open' modifier is optional.

Once an interface I declares a member function f, any type that implements I must provide a corresponding f function.

Since interfaces are open by default, the 'open' modifier in the interface definition is optional.

Example: Shape Family

include "math".*
interface Shape {
    public static func __init__ () returning Void do end
    public func area (Void) returning Float do end
}
class Ellipsis implementing Shape {
    let major (Float);
    let minor (Float);
    
    public static func __init__ (a: Float, b: Float) returning Void do
        this.major <- a;
        this.minor <- b;
    end
    public func area (Void) returning Float do
        return pi * this.major * this.minor;
        # The formula for the area of an ellipse is S = πab,
        # where a and b are the lengths of the ellipse's semi-major
        # and semi-minor axes, respectively.
    end
}
class Circle inheriting Ellipsis implementing Shape {
    let radius (Float);
   
    public override static func __init__ (r: Float) returning Void do
        this.radius <- r;
    end
    
    public override func area (Void) returning Float do
        return pi * this.radius ^ 2;
    end
}
class Rectangle implementing Shape {
    let width (Float);
    let height (Float);
    
    public static func __init__ (a: Float, b: Float) returning Void do
        this.width <- a;
        this.height <- b;
    end
    public func area (Void) returning Float do
        return this.width * this.height;
    end
}
class Square inheriting Rectangle implementing Shape {
    let size (Float);
   
    public override static func __init__ (a: Float) returning Void do
        this.size <- a;
    end
    
    public override func area (Void) returning Float do
        return this.size ^ 2;
    end
}

An interface can also use the sealed modifier to indicate that it can only be inherited, implemented, or extended within the package where the interface is defined. Sealed already implies the semantics of public/open, so if you provide public/open modifiers when defining a sealed interface, the compiler will issue a warning. Subinterfaces that inherit a sealed interface or abstract classes that implement a sealed interface can still be marked as sealed or not use the sealed modifier. If a subinterface of a sealed interface is marked as public and is not sealed, then its subinterfaces can be inherited, implemented, or extended outside the package. Types that inherit or implement a sealed interface do not need to be marked as public.

There is a built-in interface called the Everything type. All interfaces by default inherit from Everything, and all non-interface types implement the functionality of Everything. Therefore, all types are subtypes of Everything.

Volume 2: Working on Big Projects

Coming Soon.

Appendix 1: Examples

Coming Soon.

Appendix 2: See Also

Categories and References