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
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
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 definitions are basically named slots followed by the left-hand-side of a lambda:
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.
You don't have do define a class to have methods. You can simply grab a new instance and start attaching methods to it:
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
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.
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.
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
.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.
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.
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.