Cobra 0.4 adds arrays, supports space-based indentation, corrects contract semantics, makes runtime failures easier to analyze, refines the language and fixes bugs.
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
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
...
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
...
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
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.