The Wayback Machine - https://web.archive.org/web/20080208111032/http://cobra-language.com:80/docs/python/
"What's the point of Cobra given the existence of Python and IronPython?". The answer is that Cobra has several advantages over Python, and even IronPython, as described below:
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 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
def 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
Cobra features contracts (as seen in Eiffel) right out of the box. A contract states:
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:
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.
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
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" and "float".
Note that in both systems "float" math operations are faster than "decimal". However, most applications should prefer accuracy over a speed boost that may not be noticeable or needed.
Cobra compiles down to machine code right out of the box. 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 = 0 .. 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 should therefore be significantly faster than IronPython.
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:
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.
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.
Like IronPython, Cobra can use any .NET libraries, whether home grown, open source, commercial, etc. It can also benefit from .NET tools like profilers 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, or you simply wish to stick with Python while using .NET.)
Cobra shares much in common with Python:
But Cobra does not strive to be backwards compatible with Python (IronPython fulfills that role wonderfully). 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".
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.
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 migrated from C to C++ (whoops?), to Objective-C, to Java (whoops again?), to Python.
Now there is Cobra.
- Chuck Esterbrook