class Customer
def __init__(self, name):
self.name = name
def __del__(self):
pass
def __cmp__(self, other):
return cmp(self.name, other.name)
def __hash__(self):
return hash(self.name)
C# does these through constructors, destructors and well-defined methods of System.Object:
class Customer {
string _name;
public Customer(string name) {
_name = name;
}
~Customer() { // destructor
}
public string Name {
get { return _name; }
}
public override bool Equals(object other) {
return Name == ((Customer)other).Name;
}
public override int GetHashCode() {
return Name.GetHashCode();
}
}
Visual Basic (VB) does the same thing except that destructors are done via a "Finalize" method (which is what C# generates anyway). And then there is Java which is similar to C#, but uses different method names such as "hashCode" vs. "getHashCode".
Cobra largely follows VB and C# except that constructors are done through "def init" which has the unfortunate consequence of conflicting with some third party libraries which has lead to some hacks in the compiler. Even worse, the current approach cannot be extended to other backends such as JVM, Parrot and Objective-C as they won't use the same method names as .NET.
How can we solve this problem? By introducing cues.
From the dictionary:
cue: a signal for action
cue: a thing said or done that serves as a signal to an actor or other performer to enter or to begin their speech or performance.
class Customer
cue init(name as String)
base.init
_name = name
cue finalize
pass
get name from var as String
cue equals(other) as bool
if this is other, return true
if other inherits Customer
return .name == other.name
else
return false
cue hash as int
return .name.hash
These cues will translate to the correct code on whatever platform you're on (.NET, JVM, etc.). However, if you still wish to write code that is oriented towards your VM, you may still do so. For example, a .NET-centric version of Customer would be:
class Customer
cue init(name as String)
_name = name
def finalize is override
base.finalize
get name from var as String
def equals(other as Object?) as bool is override
if this is other, return true
if other inherits Customer
return .name == other.name
else
return false
def getHashCode as int is override
return .name.getHashCode
Note that "cue init" is still used, but otherwise the normal .NET methods are being overridden where appropriate.
More about cues:
The word "cue" provides a good definition ("a signal for action"). It also carves out a pseudo-namespace separate from methods thereby allowing additional cues to be added in the future. Also the three letter word fits in well with other declarative words such as "def", "var" and "get".
Here are the currently planned cues:
cue init
cue finalize
cue hash as int
cue compare(other) as int
cue equals(other) as int
cue enumerate as T*
Cues are special, so there are some special rules about them, both in general and also specific to particular cues:
-- Cue declarations are exempt from needing "is override" on them.
-- Cues cannot necessarily be reflected on by name at run-time as their run-time names may be differ by platform (.NET "GetHashCode" vs. JVM "hashCode").
-- Most cues cannot be directly invoked external to the cue. You cannot say "obj.init" or "obj.enumerate" unless you declared those methods or those are coincidentally the Object level method names correpsonding to the cues. However, this is not an issue as you don't even want to invoke a cue directly. For example, "a == b" is the canonical way to compare objects for equality, and "for a in collection" is the canonical way to enumerate through the elements of a collection.
-- One exception to "do not invoke cues directly" is that *within* the declaration of a cue, you can invoke it for purposes of implementation (e.g., base.init, .init, .thing.hash).
-- The .hash cue is also an exception. It is considered "externally visible" and can be invoked like an ordinary method. (I told you cues were special.)
-- A cue alters and adds to your class as needed to implement the cue. For example the .enumerate cue when used on .NET will make your class implement IEnumerable and IEnumerable<of> while adding a method for each, for a total of two new methods. But rather than worry about the details, just remember that you can now use your object in a for loop ("for a in mycollection, ...") and pass it where enumerables are expected ("List<of T>(...)").
-- .init requires that you invoke "base.init" or ".init" immediately.
-- "def init" will continue to work for now, but at some point will generate warnings and eventually be unsupported. But fear not. The -correct-case option will be changed to -correct and take multiple arguments such as, cobra -correct:case,init ...
-- Operator overloads may be done through cues. I haven't decided yet.
I especially like the enumerate cue:
Old way:
class FileCabinet
implements IEnumerable<of Record>
var _records = List<of Record>()
def newRecord(name as String) as Record
r = Record(_records.count+1, name)
_records.add(r)
return r
def getEnumerator as IEnumerator<of Record>
return _records.getEnumerator
def getEnumerator as System.Collections.IEnumerator
implements System.Collections.IEnumerable
return .getEnumerator
New way:
class FileCabinet
var _records = List<of Record>()
def newRecord(name as String) as Record
r = Record(_records.count+1, name)
_records.add(r)
return r
cue enumerate as Record*
return _records.enumerate
Again, with the exception of "def init", you'll still be able to do things the old/.NET way. But you'll find cues portable between platforms and in some cases like .enumerate, easier to use.
None of this is implemented yet. Get your feedback in, good or bad, while you can.