Promise: Defining Types and Classes
Once you get into TDD with statically typed languages you realize you almost always want to deal with interfaces not classes, because you are almost always looking at two implementations of the same contract: the real one and the Mock. If you are a ReSharper junkie like myself, this duplication happens without giving it much thought, but it still is a tedious verbosity of code. Add to that that prototyping and simple projects carry with them syntactic burden of static tyypes (again, a lot less so for us ReSharper afflicted) and you can understand people loving dynamic languages. Now, I personally don't want to use one language for prototyping and smaller projects and then rewrite it in another when the codebase gets complex enough that keeping the contract requirements in your head becomes a hinderance to stability.
Promise tries to address these problems by being typed only when the programmer feels it's productive and by having classes automatically generate Types to match them without tying objects that want to be cast to the type of be instantiated by the class. This means that as soon as you create a class called Song
, you have access to a Type called Song
whose contract mirrors all non-wildcard methods from the class. Since the Type Song
can be provided by any instance that services the contract, just taking an object, attaching a wildcard method handler to it creates a mock Song
.
A diversion about what would generally be called static methods
The class model of Promise is a prototype object system inspired by Ruby/Smalltalk/javascript et al. Unlike class based languages, a class is actually a singleton instance. This means that a "static" method call is a dispatch against an instance method on that class singleton, so it would be better described as a Class Method.
But even that's not quite accurate. Another difference I eluded to in my Lambda post: Whenever you see the name Song
in code and it's not used to change the definition of the class, it's actually the Type Song
. So what looks like a call to the Class Method is a dispatch to the singleton instance of the class that is currently registered to resolve when a new instance for the Type Song
is created. So, yes, it's a Class Method but not on the Class Song
, but on the class that is the default provider for the Type Song
.
If you've dealt with mocking in static languages, you are constantly trying to remove statics from your code, because they're not covered by interfaces and therefore harder to mock. In Promise, Class Methods are instance methods on the special singleton instance attached to an object. And since calling that methods isn't dispatched via the class but the implicit or explicit type, Class methods are definable in a Type.
Type definition
Type definitions are basically named slots followed by the left-hand-side of a lambda:
type Jukebox {
PlayAll:();
FindSongByName:(name|Song);
Add:(Song song);
^FindJukeboxWithSong:(Song song|Jukebox);
}
One of the design goals I have with Promise is that I want keep Promise syntax fairly terse. That means that i want to keep a low number of keywords and anything that can be constructed using the existing syntax should be constructed by that syntax. That in turn means that I'm striving to keep the syntax flexible allow most DSL-like syntax addition. The end result, I hope is a syntax that facilitates the straddling of the dynamic/static line to support both tool heavy IDE development and quick emacs/vi coding. Here, instead of creating a keyword static, I am using the caret (^
) to identify Class methods. The colon (:
), while superfluous in Type definitions, is used to disambiguate method invocation from definition, when method assignment happens outside the scope of Type or Class definitions.
Attaching Methods to Instances
You don't have do define a class to have methods. You can simply grab a new instance and start attaching methods to it:
// add a method to a class
Object.Ping:() { println "Ping!"; };
// create blank instance
var instance = Object.new;
// attach method to instance
instance.Say:(str) {
println "instance said '{str}'";
};
instance.Say("hello"); // => instance said 'hello'
instance.Ping(); // => Ping!
A couple of things of note here:
First, the use of the special base class Object
from which everything derives. Attaching methods to Object
makes them available to any instance of any class, which means that any object created now can use Ping()
.
Second, there is the special method .new
, which creates a new instance. .new is a special method on any Type that invokes the languate level IoC to build up a new instance. It can be called with the JSON call convention, which on a Object will initialize that object with the provided fields. If an instance from a regular class is instantiated with the JSON call convention, then only matching fields in the json document are initialized in the class, all others are ignored. I'll cover how JSON is a first class construct in Promise and used for DTOs as well as the default serialization format in another post.
Last, the method Say(str)
is only available on instance, not on other instances created from Object. You can, however call instance.new
to get another instance with the same methods as the prior instance.
Defining a Class
Another opinionated style from Ruby that I like is the use prefixes to separate local variables from fields from class fields. Ruby uses no prefix for local, @ for fields (instance variables) and @@ for class fields. Having spent a lot of time in perl, @ still makes me think of arrays, so it's not my favorite choice of symbol, but I'd prefer it over the name collision and this.foo
disambiguation of Java/C#.
Having used a leading underscore ( _ ) for fields in C# for a while, I've opted to use it as the identifying prefix for fields in Promise. In addition, we already have the leading caret as the prefix for Class Methods, so we can use it for Class Fields as well.
class Song {
// Class Field
Num ^songCount;
// Class Method
TotalSongs:(|Num) { ^songCount; };
// Fields
_name;
_filename;
Stream _stream;
// public Method
Play:() {
CheckStream();
_driver.Read(_stream);
};
// protected Method
_CheckStream:() { ... };
}
Just as the Caret is used both for marking Class Fields and Methods, underscore does the same for Methods and Fields: While Fields can only be protected, methods can be public or protected -- either way underscore marks the member as protected.
The method definition syntax is one of assigning a lambda to a named slot, such that
More on instance construction
Although .new
is special, it is a Class Method, so if a more complex constructor is needed or the constructor should do some other initialization work, an override can easily be defined. Since Class Methods are reflected by types, this means that custom constructors can be part of the contract. The idea is that if the construction requirements are stringent enough for the class, they should be part of the Type so that alternative implemenentations can satisfy the same contract.
An override to .new
is just a Class method not a constructor. That means that super.new
has to be called to create an instance, but since the method is in the scope of the Class definition, the instance's fields are accessible to override. There is no this
in Promise, so the use of this
in the above example is just a variable used by convention, similar to the perl programmer's usage of $self
inside of perl instance subs.
But wait, there is more!
There are a number of special method declarations beyond simple alphanumerically named slot, such as C# style setters, operators, wildcards, etc. But there is enough detail in those cases, that I'll save that for the next post.
More about Promise
This is a post in an ongoing series of posts about designing a language. It may stay theoretical, it may become a prototype in implementation or it might become a full language. You can get a list of all posts about Promise, via the Promise category link at the top.