The language is simple for several reasons:
- prototype-based, prototypes play the role of classes, used for the creation of new objects. But prototypes are also objects. Just one concept instead of two. There is no need of classes of classes for considering classes as objects. This fact alone reduces enormously the size of the language. Example:
object Program
func run {
var Int n;
// Int can be used in expression, it is 0
n = Int*Int + Int;
assert n == 0;
}
end
- methods by default are public and fields are always private
- single inheritance with interfaces
object Student extends Person
implements IDriver
end
- prototype
Any
is the superprototype of every other prototype but Nil
. Hence, basic types like Int
are subtypes of Any
var Any any;
any = 0;
any = "abc";
- there are very simple rules for type checking, nothing unusual is allowed
- Smalltalk-like syntax for declaring and sending messages. Example:
object Program
func run {
// message passing, the receiver is Out
Out print: "factorial of 5 = ";
// the receiver is 'self' in 'self fat: 5'
Out println: (self fat: 5)
}
func fat: Int n -> Int {
if n == 1 { return 1 }
else {
return n*(fat: n-1)
}
}
end
- methods can have operator names such as
+
and *
but the prototype should be immutable
object Complex
func + (Complex other) -> Complex {
return Complex(re + other getRe, img + other getImg);
}
...
end
- the language supports several kinds of literals: arrays, tuples, intervals, and maps. Example:
object Program
func run {
// literal String array of type Array
var months = [ "jan", "fev", "mar" ];
// a literal tuple
var t = [. "one", 1 .];
// 't f1' is an unary message passing
// it returns "one"
Out println: (t f1) ++ " = " ++ (t f2);
// a literal named tuple
var nt = [. name = "one", value = 1 .];
Out println: (nt name) ++ " = " ++ (nt value);
// 1..10 is an interval
for n in 1..10 { n println }
// a literal map
var nameValueMap = [ "one" -> 1, "two" -> 2 ];
// ...
}
end
- anonymous functions make it easy to code.
var sum = 0;
var f = { (: Int n :) sum = sum + n*n };
// 'eval:' calls the function
f eval: 5;
// the same as sum = sum + 25;
assert sum == 25;
sum = 0;
// sums the squares of numbers from 1 to 10
1..10 foreach: { (: Int n :)
sum = sum + n*n
};
sum println;
The function is given between {
and }
with parameters between (:
and :)
. In the above code, the function is the argument to method foreach:
- constructors are always called
init
or init:
object Box
func init { value = 0; }
func init: Int value {
self.value = value
}
...
var Int value
end
- less need of parentheses for programming
- downcast, the conversion of an object of a superprototype to an object of a subprototype should be made using the
type-case
or cast
statements. Example:
var Any any = 0;
cast Int n = any {
// only executed if 'any' can be
// converted into an Int
var sqr = n*n;
sqr println
}
Example with type-case
:
var Any any = "abc";
type any
case Int n {
(n*n) println
}
case String s {
assert s[0] == 'a' &&
s[1] == 'b';
}
- prototype fields are only initialized in their declarations or in the constructors
- the code has fewer parentheses than in most object-oriented languages because a)
{
and }
should always be given in statements if and while and b) message passing does not demand (
and )
- space is demanded before and after
=
and some operators as the relational (<
, >=
, etc) operators
var Int n;
// ok
n = 0;
// compile-time error
n=0;
if n< 0 { // compile-time error
...
}
- no checked exception handling system
- arrays are regular prototypes declared in package
cyan.lang
imported by every Cyan source code
var Array<Int> ai;
ai = [ 0, 5, 9 ];
ai[2] println;
- types need not be supplied in several situations as local variable initialization
// the compiler deduces that n is Int
var n = 0;
- method arguments are always evaluated from left to right
- specific binary operator
++
for converting both sides to String and concatenating them
assert "0" ++ 1 == 0 ++ "1";
assert 0 ++ 1 == "01";
- non-shared fields and methods can only be called using the prototype in which they are declared, as in
MyList maxSize
, or inside the prototype without an explicit receiver (as maxSize
)
- prototypes with operator methods should be immutable. That helps preventing operators being used for abstractions that are not related to mathematics
- prototypes declaring methods with ‘package’ visibility can only be inherited by prototypes of the same package. That prevents some confusing code in which a method defined in prototype A is not visible in B, subprototype of C, and again visible in a prototype C that inherits from B. See article ‘Traps in Java’
The language does not support:
- nested prototypes and methods
- overloaded methods unless they are clearly marked with keyword
overload
- default parameters in methods
- complex rules in generic prototypes
- the
return
statement inside anonymous functions and, therefore, inside try
blocks used for implementing exception handling
- implicit conversion between basic types. Value
1
is not automatically cast to 1.0
, for example. All conversions should be made by message passing as in n asDouble
for converting an Int
variable n
into a Double
- default values for prototype fields, all of them should be explicitly initialized
- extension methods
- postfixed unary operators
++
and --
. The code ++n
is a statement equivalent to n = n + 1