Gallery
Mochaccino Sprint 1
Share
Explore

icon picker
Testing

You need the willingness to fail all the time. You have to generate many ideas and then you have to work very hard only to discover that they don't work. And you keep doing that over and over until you find one that does work.
More often than not, our initial set of features and syntax will present some difficulties when being implemented. Down the road, we may realise that certain things don’t work the way you would expect them to. To overcome this, we must first test our language concept against sample programs. In our case, we tried to come up with the code for the core libraries that would be bundled together with the language. Let’s take a look at what we tried and what issues cropped up.

Issue #1: Core libraries implementation

We first tried creating the str struct that provides the primitve string type.
package string;

struct str {
@elementary
constructor(obj<dyn>)<str> {
// string conversion
}
}
Now, to convert an object to a string, we could use two different methods:
Provide the string methods directly within the struct.
Create a String module that provides the necessary methods such as toString() and startsWith().

Providing all methods directly from the struct

Trying method 1, we have:
package string;

struct str {
@elementary
constructor(obj<dyn>)<str> {
// string conversion
}
func toString()<str> {
// string conversion
}
}
But that’s where we run into a problem.
var a<str> = "hello";
var b<num> = 2;
Now, who in the right mind would call toString() on an object that’s known to be a string? Instead, we would want to call b.toString(). However, that’s not possible because we’ve coupled the method to the str type. So now it would make sense to try method 2.

Creating a module for non-elementary methods

package string;

struct str {
@elementary
constructor(obj<dyn>)<str> {
// string conversion
}
}

module String {
func toString(obj<dyn>)<str> {
// string conversion
}
}
So that we would have to do something like String.toString(b).
That is actually pretty neat and acceptable, but let’s try a third method that involves struct polymorphism (cuz why not).

Struct polymorphism

package string;

struct type<T> {
@elementary
@extensible
constructor(obj<dyn>)<T> {
return obj;
}
func toString()<str> {
// string conversion
}
}

struct str extends type<str> {
@elementary
constructor(obj<dyn>)<str> {
super(obj);
return obj.toString();
}
}
We now have a new struct annotation, @extensible, a super() method, and we also see type arguments coming into play. Whenever primitives type like num or arr are created, they will have to extend the type struct. We can also put information such as runtimeType into that struct to make it available across all types. One caveat, though, is that only structs that extend type define proper objects. Those that don’t do so end up permitting the creation of objects without a hashCode or any runtimeType. Even if we changed our type hierarchy to have dyn at the top, it still wouldn’t solve the problem.

Conclusion

Our best bet right now is, therefore, method 2, where we have separate modules to provide common functionality across structs. Yet there lies in our path one more hurdle to overcome—finding out the runtimeType of any object. We can either put that into a Developer Tools module or turn it into a keyword.
var a<num> = 10;
Toolbox.inspect(a).runtimeType;
versus
var a<num> = 10;
typeof a;
In such cases it’s really up to you, the language designer, to pick whichever suits your fancy.
We decided to treat it as a method in the Toolbox module.
Note
Even though some methods may be implemented by modules, it is important to know that methods which modify the objects themselves (e.g array.add()) should be defined in structs as they have access to the this keyword.
Through our little experiment, we discovered the @extensible annotation which could help us in extending elementary structs. But would it be better to mark non-extensible structs as @nonext? Should our structs be extensible by default? Perhaps it might make more sense to have structs that are not extensible by default. That way, whenever an extensible struct is being declared, the developer has to carefully write code that takes the data of the child structs into account. Essentially, if a struct is not marked @extensible, it wasn’t meant to be extended. If it is, then it has to be prepared to handle data accordingly. But that isn’t the end. We still need to control the inheritance of props and methods.

Issue #2: Enum-like behaviour

We know what enums are, but what if we tried to do that in Mochaccino? Since there is no enum keyword available, we’ll have to do it using structs and modules.
struct PaymentOption {
@elementary
prop cash<PaymentOption> {
get { return _________ }
}
}

var paymentOpt<PaymentOption> = PaymentOption.cash;
Before we get to returning a value of type PaymentOption, notice that the struct has to be marked as @elementary because it is being used as a type annotation. This implies the presence of a constructor:
struct PaymentOption {
@elementary
constructor()<PaymentOption> {
return _________;
}

prop cash<PaymentOption> {
get { return _________ }
}
}

var paymentOpt<PaymentOption> = PaymentOption.cash;
We now have 3 problems:
Return value of the cash property
Return value and parameters of the constructor
Controlling type conversions to this struct (123::<PaymentOption>)

Return value of the cash property

Creating an enum type

Let’s try creating a primitive type that stores enums:
struct enum<T> {
@elementary
@extensible

constructor(______)<enum<T>> {
return ______
}
}

struct PaymentOption extends enum<PaymentOption> {
@elementary
constructor(______)<enum<PaymentOption>> {
return ______
}
}
Nope, there are way too many missing pieces for this method.

Creating an @enum struct annotation

struct PaymentOption {
@elementary
@enum

prop cash;
prop card;
}
Pretty neat, isn’t it? Looks similar to an enum but also doesn’t require a new declaration block to be created. Since we’ve used the @enum annotation here, the compiler will fill in the return types of the properties to be <PaymentOption>.
Let’s also take this opportunity to note that the @enum annotation is very flexible, and can be used on top of existing annotations. However, the enum properties cannot be inherited by the child class.

Controlling type conversions to the struct

The default constructors do not allow type conversions to take place:
struct PaymentOption {
@elementary
@enum

prop cash;
prop card;
}

var a<PaymentOption> = "cash"::<PaymentOption>;
Though you could define constructors to allow conversions from specific types:
struct PaymentOption {
@elementary
@enum
constructor(obj<str>) {}

prop cash;
prop card;
}

var a<PaymentOption> = "cash"::PaymentOption;
In which case Mochaccino will help you by creating an instance of the PaymentOption.cash constant.

Conclusion

We now have the @enum annotation which modifies the behaviour of constructors and props in marked structs to allow the definition of groups of constants.

Share
 
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.