Release Notes

Summary

Cobra 0.4 adds arrays, supports space-based indentation, corrects contract semantics, makes runtime failures easier to analyze, refines the language and fixes bugs.

Additions / Major Improvements

class Example shared def sum(nums as int[]) test nums = @[1, 2, 3] # array literal; like list but prefixed by @ assert nums.length == 3 assert nums[2] == 3 assert Example.sum(nums) == 6 body sum = 0 for num in nums sum += num return num def main parts = 'a|b:c'.split(@[c'|', c':']) # split() expects an array of chars assert parts.length == 3 for part in parts print part
  • non-false
  • non-zero
  • non-zero-char
  • non-nil

Note that blank strings and empty collections are now considered to be true because they are non-nil (previously they were false for having a length or count of zero).

This eliminates most is not nil and <> nil from the code, while adding .length and .count when dealing with strings and collections. In the source code of the Cobra compiler itself, the is not nil cases outnumbered the "truthfulness of an object" case by 2 to 1. So there is a net reduction in code. And for all the times one might have forgotten to say is not nil when only non-nilness was required, Cobra programs are now more efficient. There is a more thorough blog entry on the topic.

if stuff # stuff is not nil .process(stuff) ... if stuff.count # have non-empty stuff for item in stuff print item ...

Contract Improvements

Contracts are a major feature of Cobra (largely inspired by and copied from Eiffel). Previous versions of Cobra had some incorrect semantics and deficient syntax regarding contracts. These are now corrected by the following improvements:

Requirements in the inheritance chain are "OR"ed at runtime. This means that a subclass can override a method and provide an alternative requirement for execution of the subclass method. However, a call to base.methodName will still require the original contract (although no base call is ever required).

In that context, RequireException has been given a next property that chains them together.

Note that a missing require for the top level method (or property) is like require true. But an overriding method with no require will inherit the require of its base method.

Ensurements in the inheritance chain are "AND"ed at runtime. This means that a subclass can override a method (or property) and add additional ensurements. It also means that the overriding method must still conform to the ensurements of the base class whether it invokes it or not.

To highlight the semantics of contract inheritance, the syntax in an overriding method is now or require (rather than only require) and and ensure (rather than only ensure).

In summary, overriding methods can strengthen requirements and weaken ensurements.

And now an example:

class ContiguousList<of T> implements IList<of T> def insert(index as int, item as T) require index >=0 and index < count ensure .count = old .count + 1 this[index] is item body ... class NonContiguousList<of T> inherits ContiguousList<of T> """ Allows insertions past the end of the list. """ def insert(index as int, item as T) or require index >= 0 body ...

Refinements / Minor Improvements

x = '' if x is not nil # --> error: The variable "x" can never be nil because its type cannot be nil. print x

You will not get this error if x's type is System.Object because that may refer to boxed primitives such as bool and int, which will be treated correctly at run-time.

class Document pro body as String ... ... print doc.body
class CobraCore shared pro willCheckInvariant as bool pro willCheckRequire as bool pro willCheckEnsure as bool pro willCheckAssert as bool pro willCheckNonNilClassVars set willCheckAll as bool

Fixes

class Foo var _name as String def init ...

The variable _name cannot be nil at the end of init. You must set it to a non-nil value or change the type to String?. Otherwise, an assertion will fail at run-time.