Comparison to Python

"What's the point of Cobra given the existence of Python?" The answer is that Cobra has several advantages over Python as described below:

Better error checking

Cobra has more compile-time error checking. For example, Python can throw a NameError at any point during run-time due to mispelling a variable name, but Cobra catches these errors at compile-time. Furthermore, because Python is throwing run-time exceptions for some of these simple errors, they only get reported one at a time and each instance requires a new run of your program. Cobra saves time when it reports multiple errors at once.

Cobra allows simple local variable assignments like Python, but gives a warning if they are never used. This catches typos that Python simply ignores:

class Foo def bar(name as String) if name is nil namee = '(noname)' # <-- typo gives warning in Cobra, silent bug in Python

Cobra also has optional static typing which can clarify the interface to a method and catch more errors at compile-time:

class Person def drive(v as Vehicle) pass

The typed arguments are convenient to include and enable the compiler to catch problems before execution. However, if you want dynamic variables that can be assigned to anything, passed anywhere and have any methods invoked on them with no complaints from the complier, then you can have that:

class Person def drive(v) # any usage of v is late bound print v.name

Python programmers must eliminate run-time "None errors" from their programs where accessing "obj.foo" gives:

AttributeError: 'NoneType' object has no attribute 'foo'

And likewise, C# and Visual Basic programmers must deal with the equivalent (the NullReferenceException). But one of Cobra's more unique features is compile-time nil checking ("None" in Python is called "nil" in Cobra). Cobra types that are not suffixed with a question mark are not allowed to be nil, while types that are suffixed are called "nilable types":

class Person var _name as String cue init(name as String) _name = name class PeopleFactory def makePerson(name as String?) as Person return Person(name) # <-- compile-time error. Cannot pass String? for String # one resolution: def makePerson(name as String?) as Person if name return Person(name) # compiler understands that name is non-nil at this point else throw FallThroughException(name) # another resolution is to change the type: def makePerson(name as String) as Person return Person(name)

So in Cobra, these nil/null/None errors are caught at compile-time and hardly ever take place during execution. Getting them out of the way early boosts productivity.

Like Python, you can run a Cobra program directly with no direct mention of compilation or use of make files, nant, etc.:

> cobra hello.cobra
Hello, world.

You can also instruct Cobra to only compile:

> cobra -compile MyProgram.cobra Module1.cobra Module2.cobra
# or a terser version of the same thing:
> cobra -c MyProgram Module1 Module2

Contracts

Cobra features contracts (as seen in Eiffel) right out of the box. A contract states:

  1. What is required to call a method
  2. What is ensured when the method is returned

Both are expressed as a list of boolean expressions, the same kinds of expressions that you would use in an assert statement.

class Person def drive(v as Vehicle) require not v.hasDriver v.isOperable ensure v.miles > old v.miles body ...

Contracts have numerous benefits:

Unit tests

Cobra features unit tests right out of the box:

class Foo get copy as Foo """ Returns a new Foo that is equal to the original. (Cobra has docstrings, too). """ test f = Foo() c = f.copy() assert c inherits Foo assert f is not c assert f == c ensure result is not this result == this body ...

Allowing unit tests to be specified right with the method (or class) that they test has similar benefits to putting documentation right with methods:

Another benefit is that the tests become a form of documentation for the method right along side the docstring—the tests are examples.

A similar effect can be achieved with the Python doctest utility, but that utility has the tests being placed inside the doc strings. In Cobra, the explicit test section means that tests can enjoy syntax highlighting, IDE autocompletion, etc.

Accurate Math

Out of the box, Python performs arithmetic incorrectly:

Python 2.5 (r25:51918, Sep 19 2006, 08:49:13) 
>>> .1+.1+.1+.1+.1+.1+.1+.1+.1+.1  # .1 added 10 times should come out to 1.0
0.99999999999999989
>>> assert 1.0 == .1+.1+.1+.1+.1+.1+.1+.1+.1+.1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

And Python still answers 0 for "4 / 5" even though 0.8 is the correct answer. Although you can correct this to some extent with from __future__ import division you'll still get a slightly wrong answer:

>>> from __future__ import division
>>> 4 / 5  # normal division
0.80000000000000004  # <-- there is an extra '4' at the end
>>> 4 // 5  # integer division
0

(Recently, it appears that Python's repr() has been "enhanced" to display 4/5 as 0.8, but AFAIK the underlying representation is still a base 2 floating point number.)

The problem is that Python defaults to a binary floating point type even though most numbers that people input are base 10. To help address this Python offers an additional Decimal type that computes the correct numbers, but it must be used explicitly and, some would say, awkwardly:

from decimal import Decimal
...
print Decimal('4') / Decimal('5')
# gives:
0.8

To get the same effect in Cobra:

print 4 / 5 # gives: 0.8

So Cobra does the inverse, defaulting to an accurate decimal type and offering the floating point type as an option. A simple "f" suffix on a number such as "0.1f" gives the 64-bit floating point value. The types are builtin with the names "decimal", "float", "float64" and "float32". The type "float" is an alias for "float64".

Note that in both languages, "float" math operations are faster than "decimal". However, most applications should prefer accuracy over a speed boost that may not be noticeable or needed. For the other applications that really require more speed or compatibility with floating point based libraries, Cobra offers a command line option "-number:float64" which changes the type of literals like "1.0" and "0.5" to to float64. Furthermore, Cobra provides a built-in number type which defaults to decimal but changes to float64 with this option. You can also specify "-number:float32" or "-number:decimal" although the latter is redundant.

Speed

Cobra compiles down to machine code right out of the box (first to byte code and then to machine code via the virtual machine). And Cobra favors static types for local variables by inferring them from their assignment. This promotes more compile-time error checking, especially when invoking library methods. But it also promotes speed.

Python has solutions for improving speed for its developers: You can write some of your Python modules in C and wrap them in SWIG. You can use Pyrex. Or you can stay in Python and "bring in" C or C++ via Inline or Weave. But with Cobra the speed is builtin from the beginning. You don't get kicked out to another language or get forced to assimilate another tool.

Here is an example of type inference:

class Foo def bar i = 1_000_000 # i is typed as 'int'. The underscores help readability (and are optional) for j in i # numeric for loops are screaming fast .doSomething(j) # method calls are fast def doSomething(i as int) pass

Cobra's performance is close or equal to that of C# and Java, and is therefore significantly faster than Python. IronPython claims to be around 1.8 X faster than Python, but this is still far slower than Cobra. Also, some users have found that IronPython can run significantly slower than CPython and Jython.

By the way, Cobra also has an "enumerable" for loop, in case you were wondering:

for item in stuff print item

Note that speed isn't just a benefit for the final delivery of your software. Speed can affect the development cycle itself:

  1. Edit the code.
  2. Run the program.
  3. Analyze the results.
  4. Repeat.

Some applications—including financial analysis, simulation, search, neural networks, games, and more—require numerous CPU cycles which can cause Step 2 ("Run the program") to become a bottleneck during development. Cobra enables a tighter development cycle by offering high level coding and fast execution simultaneously.

Because Cobra has no Global Interpreter Lock (GIL), it can run threads in parallel up to the number of cores available. Combined with its inherent speed, you can pack a lot of computation in one process on one computer.

Designed for .NET/Mono

Cobra creates the same kinds of classes, interfaces, method signatures, etc. that are found in C# and Visual Basic. Consequently, you can create a class library in Cobra and comfortably vend it out to C# and Visual Basic programmers on the .NET/Mono platform. In fact, it's an explicit goal of Cobra to play nice with .NET's flagship languages so that introducing Cobra to work environments is both technically and politically feasible.

This is different than IronPython whose class libraries are not going to "feel right" or even be readily accessible to other .NET programmers.

Cobra can use any .NET libraries, whether home grown, open source or commercial, without any "bridging" or "wrapping". It can also benefit from .NET tools like profilers, debuggers and ORMs.

(By the way, IronPython is certainly the best choice if you have lots of existing Python code you wish to run on .NET.)

Syntactic improvements

Cobra shares much in common with Python:

But Cobra does not strive to be backwards compatible with Python. This opens the door to some improvements. For example, here is a read-write property in Python:

# Python
class Person:

    def __init__(self):
        self._name = '(noname)'

    def _get_name(self):
        return self._name
    def _set_name(self, value):
        assert isinstance(value, str)
        assert value
        self._name = value
    name = property(_get_name, _set_name)

Contrast that with the equivalent Cobra:

# Cobra class Person var _name = '(noname)' pro name as String get return _name set require value _name = value

(Note that Cobra does not require indentation for a require that has only one condition. This shorter syntax was added because that happens frequently.)

Cobra allows embedding expressions in string literals—often called "interpolated strings" in other languages. This turns every string literal into a mini-templating language (whose expressions are just ordinary Cobra expressions). The embedding is done with surrounding brackets to make it extra clear where the expression ends:

print 'Hello, [name].' ... print 'Will execute:' print '> [cmd] [args]' ... print to dest, '<ul>' for line in _lines print to dest, '<li>[.htmlEncode(line)]</li>' print to dest, '</ul>' ... print r'No embedding here. [edit this]' # raw string

In Python, it's common to augment assertions with a repeat of the expressions in the condition so that when the assertion fails, you'll get useful info to troubleshoot the problem. An example shows this better:

assert obj.total > minAmount, 'obj.total=%r, obj=%r, minAmount=%r' % (obj.total, obj, minAmount)

Cobra does this automatically, so you would instead write:

assert obj.total > minAmount

If the assertion fails, Cobra gives the value of every subexpression (obj.total, obj, minAmount) in the assertion exception's message:

Unhandled Exception: Cobra.Lang.AssertException:
location = Program.cobra:9
info = nil
expressions:
    obj.total > minAmount = false
    obj.total = 5.2
    obj = SampleObject
    minAmount = 10.0
at Program.Main()

There are some other minor improvements: Cobra drops colons (:), quadruple underscores, and self-itis while spelling out "else if".

Self-hosted / Self-implemented

This point is the most subtle, but still interesting. The Cobra compiler is implemented in Cobra. This means that the entire time the implementors are maintaining Cobra, they are using Cobra. This really tightens up the feedback loop on usability and bugs!

This is in contrast to most other languages that are typically implemented in some other language (often a more primitive one). That practice greatly reduces the time that the language maintainers spend using their own language.

A notable exception is the Novell Mono C# compiler which is written in C#. However, since their goal is to embrace and comply with the C# standard (as it should be), this practice won't necessarily lead to numerous language improvements like it does with Cobra.

Disadvantages

  1. Documentation needs expansion.
  2. Cobra classes are not malleable at runtime like Python classes are.
  3. Like all budding languages, some people will pass over Cobra because it is new and, therefore, not yet popular. Fortunately, many people don't balk at "newness" as evidenced by the fact that many new languages of the past have gained popularity after their introduction, including Python and C#. The same will happen for Cobra, in time.
  4. Cobra is not yet available for the JVM (but a port is underway).

The Upshot

The upshot of all of the above is the same as the original reason for creating Cobra:

I believe that these features and this approach improve developer productivity significantly. And whether you program for a living, for fun or for both, getting more done in less time is just plain enjoyable. It's the reason so many of us, in the past, migrated from one language to another.

Now there is Cobra.

- Charles Esterbrook

See Also