CHAPTER 2
In addition to class types, Julian has 5 primitive types. It also has an overarching Any type that covers both class and primitive types.
The root type in the class hierarchy is Object. All the other classes, including built-in types, are offspring of this class. The built-in types are: Array, Function, String, Enum, Attribute.
The primitive types are: int, bool, char, byte, float.
All primitive types can be initialized, explicitly or implicitly, with literals.
int i = 5;
bool b = true;
char c = 'x';
Console.println(3); // the argument anonymously initializes an int value with 3
If a variable of primitive type is declared without initializer, it will have the default value for that type. For example, an int will be 0, a bool false.
These types are not subclasses of Object and are not class type at all. To certain degree one primitive type can coerce its value to another, but this is not always true. For example,
int i;
float j = 3.7;
i = j;
Console.println(i); // 3
In general, such conversion can only happen when the target type has same semantics as the source (say, both numeral), and has a higher precision or wider range than the source. If this is not the case, an explicit casting is required.
char a = 'a';
int i = (int)a; // semantic difference
byte b = (byte)i; // target has a shorter range
If casting is missing, such assignments will throw TypeIncompatibleException.
Integer type supports notation for different base. Base-2 notation starts with 0b, while base-16 0x. For example, the following variables are equal.
int i02 = 0b1010;
int i16 = 0xA;
int i10 = 10;
Like primitive types, String has a language-level alias and supports literal notation.
string s = "abc";
Among class types String is unique in its assignment semantics. Like in Java and C#, while class instances are copied by reference, String is by value.
string s = "abc";
string t = s;
// now s and t are two copies of "abc"
String supports a special operator: '+', which concatenates strings and values of other primitive types. Primitive types are converted to string by its natural value in the concatenation. Note, however, that there must be at least one string as an operand in the first '+' operation of a chained concatenation to force the expression to convert to string type.
string s = "abc" + 1 + true; // abc1true
string t = 1 + true + "abc"; // throws!
String has a special field: length, which stores the logical length of this string. This means if the string contains UTF8/16 characters, each character accounts for only one in the total length despite its actual storage usage.
String defines a number of instance methods to derive or generate new string, but none of these methods would in any way change the current string. For more info on these methods, see here.
Array is another special class, providing un-scalable vector storage.
To declare an array variable, specify any type other than array itself with one or more bracket pairs. The type Array is not meant to be initialized with a constructor.
int[] ai;
string[] si;
MyClass[][] d2array;
Like other classes, arrays declared without initializer is pointing to null. To initialize an array, there are two ways. First, one may declare an array with a given length for each dimension. This way, each element is initialized with the default value of the element's type. An int's default is 0, a string null, etc. Note in the cases of multi-dimensional array, each sub-array will be created and recursively initialized.
int[3] ai;
string[2][3] si2;
The second way is to use a new expression.
int[] ai = new int[]{ 1, 2, 3 };
string[][] si2 = new string[][] { new string[] { "a", "b", "c" }, new string[] { null, null, null } };
When initializing in this fashion, the rank of each dimension is to be determined by the actual number of values encountered in the initializer. Since Julian doesn't support jagged arrays, an initializer providing sub-arrays of different size at the same dimension will engender runtime error. Also, the initializer cannot be mixed with default initialization (the first way). Julian doesn't support partial default initialization either.
Arrays exposes a special field, length
, to provide information about the array's length. It also supports indexer operator to access to its data. The classic for-loop on an array:
int[] ai = ...;
for (int i = 0; i < ai.length; i++) {
int x = ai[i];
if (x < 0) {
ai[i] = x * 2; // double any negative member
}
}
Fast for-loop also works on array:
int[] ai = ...;
for (int i : ai) {
... ...
}
String and Array are not the only types on which you can use indexer syntax or perform fast for-loop. As long as a user-defined type implements IIndexable, the indexer is supported as a syntax sugar laid over the getter and setter methods. To be used in a fast for-loop, the type only needs to implement IIterable.
We will come back to Array when we talk about other collection types provided by the standard library of Julian. By then we will be equipped with more knowledge to understand a few additional APIs that allow us to use Array in a chained calling style.
Julian's type system originates from compiled languages which bifurcate the basic types into two archetypes: the reference type and the value type, and there is no type compatibility between the two, except for certain cases of conveniences. Since those languages are equipped with static tooling to ensure the proper use of these types, the bi-archetype system generally works well. Julian, however, is a purely interpreted language. It would be unrealistic to always let users provide interface targeting both archetypes. To solve this problem, Any is introduced to represent the single grandparent of all the types. In other words, a variable of any types can be assigned or passed to a variable of type Any.
In Julian, use keyword var to declare Any type:
var v; // Without initializer, v is null.
v = 5;
v = "abc";
Object o = v; // OK. At this point v is actually a String.
int i = v; // Runtime error.