A language that doesn’t have everything is actually easier to program in than some that do. If we’re creating a programming language, we probably have some features that set us slightly apart from other languages. In the first Features draft, we should aim to consolidate all these “experimental” features. We need to clarify how they work and whether they can integrate well with the rest of the language.
Static Typing
Type Annotations
Like any statically-typed language, Mochaccino uses type annotations to give variables and values types. The default type inferred is dyn (dynamic). You can implicitly define a variable’s type too:
Similarly, whenever a type annotation is expected, it should be enclosed in angled brackets. You can also provide type arguments where appropriate:
var b<map<str, num>> = {'a':1};
The actual type, the part between the angled brackets, is known as a type literal. Sometimes, type annotations like the ones above are expected, whereas type literals are expected in other cases.
Built-In Types
The default types in Mochaccino are:
Custom Types with Structs
To define a custom type, you use the struct keyword. This allows you to define an interface that can be used to constrain data with certain properties and methods.
Structs
DOT Type System
Before we get into the thick of it, let’s understand a core principle that structs in Mochaccino were built around — the Data, Object, Type (DOT) Type System. Data refers to raw, low-level data such as a map. A Type is like a scaffolding structure over the data. It provides a systematic way to work with the data to view and modify the data. An Object is an instance of a piece of Data combined with a Type. Confused? Check out this simple example:
struct JSON { // Type
...
}
var data<map> = {...}; // Data
var a<JSON> = data::JSON; // Object
Bear in mind, even primitive types like str and map are Types too, providing a functional layer for even lower-level data. This little elaboration should clarify the role of structs in Mochaccino, and also certain terms that you're going to see later on.
Types of Structs
There are two types of structs in Mochaccino: elementary and protocol structs. Elementary structs allow you to define basic types like JSON and str which can hold values. Protocol structs, however, define interfaces for structs and modules (basically like structs for structs). If the previous sentence makes no sense, here’s an example:
Let’s say we have the struct str. It has the length property, right? But to return the length of the string, the struct must have access to the data it is wrapping. In such cases, using an elementary struct gives you access to the this keyword, which allows you to reference the data wrapped by the struct from within the struct.
So the length property of the str struct might have something like:
...
prop length<num> {
get { return this.chars; }
}
...
However, protocol structs do not wrap over a value. Instead, they are used for struct polymorphism and to define interfaces for structs and modules:
struct entryPoint {
@protocol
func main(args<arr> = [])<void>;
}
module Main implements entryPoint {
func main(args<arr<str>> = [])<void> {
Console.log('hello');
}
}
We’re not going to ever do something like:
Constructors and Type Assertions
An elementary struct can have a constructor, which is empty by default, that converts data to its own type. The constructor is called whenever a type conversion (aka assertion) is taking place. A type conversion looks like this:
On the left is the value that needs to be converted and on the right is the conversion target type. In this case, the constructor for str is called with 12 as the argument. The constructor can then do some initialisation work like storing the "inner value" provided.
To define constructors in structs:
struct str {
constructor(value<dyn>) {
str.toString(value);
}
func compareFirstChar(firstChar<str>)<bool> {
return (firstChar==this.chars[0]);
}
...
}
Some things to note:
The parameters of the contructor will be the data types that can be converted to this type If you want to have only a few types that can be converted instead of accepting all types, use optional parameters: constructor(
value<arr>,
value<num>
)<str> {
str.toString(value);
}
Don’t worry about any errors for repeated identifiers — Mochaccino will evaluate constructor arguments differently from ordinary functions. Hence, the return type of the constructor has to be the same as the struct: constructor(value<dyn>)<num> {
str.toString(value);
}
Elementary struct constructors cannot use the return keyword. Guarded Keyword
The guarded keyword in Mochaccino causes the succeeding statement to only be executed if it does not throw an error. If an except clause is specified, then the offending statement will be run, and the error will be handled by the except clause. This is typically used in type assertions, where one type is converted to another.
guarded 12::Service except pause; // program exits without errors
guarded 12::Service; // no type conversion takes place
In both of the cases above, no error is thrown and the program exits with code 0.
Type Arguments
How about structs like map? How do we enable structs to receive type arguments?
struct map<K, V> {
@elementary
constructor(...) {
...
if (this.K.type == str) {...}
...
}
}
Props
Props, short for properties, are used to define getters and setters together in structs. Just define the name and type of the property, and the getter’s return type as well as the setter’s paramaters are automatically filled in.
struct JSON {
...
prop id<num> {
get {
return this['id'];
}
set {
this['id'] = id;
}
}
...
}
Notice how the parameter id is exposed within the setter at runtime. Of course, you can choose to have only a getter or setter.
Static Methods
Elementary structs can have elementary methods and static methods. Elementary methods, as their name suggests, are called on Objects directly. But you can also define static methods in elementary structs:
struct A {
static func doStuff()<void> {
...
}
}
Struct Polymorphism
Implementing Structs
Implement: provide concrete implementations of abstract members
Extending Structs
Extend: define more abstract/concrete members
Debugging
Breakpoint Keywords
There are two breakpoint keywords in Mochaccino, ok and notok, which are used for debugging. The ok keyword logs its line number and column position when reached. The notok keyword prints its position, the result of the preceding statement to the console, and exits the program. Furthermore, their functionality can be extended through the use of debug flags, discussed later.
if (someVar.someMethod()!='abc') {
notok;
} else {
ok;
}
This is the console log for the above code:
exit[0]: notok
line: 3, column: 3
Debug Flags
Taking the same example as just now, this is how we can use debug flags for richer console logs:
if (someVar.someMethod()!='abc') {
#label: $someVar.someMethod is null
notok;
} else {
ok;
}
The above code will result in a log like this: