Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Expressions

An expression is, broadly defined, anything that gives a value. Or more precisely, anything that can give a value under some circumstance. Things like control structures are therefore not expressions, since they have no value.

Declarations of functions, classes, and namespaces in MiniD are actually just syntactic sugar for expressions being assigned into variables. In this way, everything in the language can be defined in terms of expressions and statements that do something with the values those expressions yield.

I won't cover every expression in gory detail here. For an exhaustive, but much drier and more in-depth explanation of them, see this. Later chapters will also give more information on some expressions.

Logical Expressions

These are expressions which perform some kind of boolean logic on their operands.

Keep in mind that MiniD allows any type to be used as conditions, not just booleans. Boolean false, null, integer 0 and float 0.0 are all false; any other value or type is true.

The first is the conditional operator, which is the only operator which takes three operands. This is familiar to anyone who's used C or its derivatives.

The conditional operator evaluates the first expression. If it's true, the whole thing evaluates to the second expression. If it's false, the whole thing evaluates to the third expression. It's a bit like an 'if-else', but in expression form.

>>> true ? 1 : 2
 => 1
>>> false ? 1 : 2
 => 2
>>>

Two other logical expressions are logical and (&&) and logical or (). Like in other C-style languages, these perform short-circuit evaluation. That is, they evaluate the first operand. If it evaluates to a truth value which will make the second operand irrelevant, they will skip evaluation of the second operand. For example, with logical and, the result will be true only if both sides evaluate to true. But if the left side evaluates to false, there's no need to evaluate the right side since there's no way for the result to be true; therefore it stops evaluating and simply yields "false." Logical or works similarly, but it will stop executing if the first value evaluates to true. I hope I don't have to demonstrate these. You should know how to program a bit already!

The last logical operator is the single-operand logical not operator (!). This takes a value of any type, and returns the inverse of its truth value. So "!true" gives false and so does "!5".

Arithmetic and Bitwise Expressions

Now I really hope I don't have to demonstrate these. Not the arithmetic operations, anyway.

MiniD has the arithmetic operators you'd expect: +, -, *, /, and %. They work completely as expected for the numerical types (ints and floats). As mentioned before, if one operand to these operators is an int and the other is a float, the int will be promoted to a float and the operation will be performed on the two float values.

MiniD also has the negation expression, which takes only one numerical operand and returns the negation of that, such as "-x".

The bitwise expressions should also be familiar to many programmers with a C-style background: &, |, ^, <<, >>, and >>>. The only one you might not have seen before is >>>. This operator performs an unsigned right-shift. Whereas >> will keep the same sign but affect only the magnitude of the number, >>> treats the number as an arbitrary bit pattern and shifts 0s into the top bits regardless of the original sign bit. This only has consequences for negative numbers:

>>> 16 >> 1
 => 8
>>> 16 >>> 1
 => 8
>>> -16 >> 1
 => -8
>>> -16 >>> 1
 => 9223372036854775800
>>>

You'll see that "-16 >> 1" is -8, since >> preseves the sign, but "-16 >>> 1" is very different.

There is also the unary bitwise complement expression, ~, which takes and integral operand and returns the bitwise inverse of it (that is, the number with all 0s flipped to 1s and all 1s flipped to 0s).

Concatenation and Appending

Concatenation is the act of putting two lists of items together into one. There are two list-like types in MiniD, strings and arrays, and both support concatenation. If you concatenate two strings or two arrays, the result is a string or array, as you'd expect. The more interesting stuff happens when you mix types.

You can concatenate strings and characters together to form new strings, as well as concatenating characters together to form strings. Strings and characters can't be concatenated with anything else though.

Arrays can be concatenated with arrays, as mentioned above, but they can also be concatenated with any other type. When this happens, usually the object is just concatenated either before or after the other elements in the array; that is, in "a ~ x" where 'a' is an array and 'x' is not, the result will be a new array with the elements of 'a' followed by 'x'. The order is reversed if the expression is "x ~ a". There is one more case, and that's when metamethods are involved, but we won't get to them until later.

Comparison, Equality, and Identity Expressions

The comparison operators (<, <=, >, and >=) are borrowed from other C-style languages, and work exactly as one would expect. There is one more comparison-related operator that MiniD has, the three-way comparison operator (<=>). (This is affectionately dubbed the "UFO" or "spaceship" operator like in Perl.) This operator gives, instead of a boolean value, an integer value representing the sense of the comparison. In the expression "a <=> b", the result will be an integer less than 0 to signify that a is less than b; an integer greater than 0 to signify that a is greater than b; or the integer 0 to signify that they are equal. The three-way comparison operator is very useful in writing comparison predicates and overloading the comparison metamethod.

MiniD, like D and unlike many other languages, distinguishes between equality and identity. Equality is when two objects are equal because their values are equal. Identity, on the other hand, is what it sounds like -- an object is only identical to itself, since no other object has its identity. More or less, two values are identical only if they're the same type and the same object. For value types, identity and equality are one and the same. 5 is equal only to 5, and since there is only one 5, 5 is identical only to 5. For reference types, the two concepts diverge. Two user-defined objects are equal if their comparison metamethod says so; but an instance of a user-defined object is identical only to itself.

Another difference between equality and identity is that equality will throw an error if the two values cannot be compared, but identity never throws an error; it only returns false.

The equality operators are == and !=. They work exactly the same, except != simply inverts the truth value of the equality test. The identity operators are 'is' and '!is', and again, '!is' is just the logical inverse of 'is'.

One last word -- just like the mathematical operators, if one operand of an equality expression is an int and the other is a float, the int will be cast to a float and the comparison performed on those values. That is, "1 == 1.0" is true. But keep in mind that 1 and 1.0 are not identical, since they are different types (that is, "1 is 1.0" is false).

Function Calls

What use is a function if you can't execute it? MiniD has three kinds of function calls: "normal" function calls, method calls, and supercalls.

Normal function calls is when you just call a function with some parameters. More in a bit.

Method calls occur when you access a field from an object and then call that. That is, whereas "f()" is a normal function call, "a.f()" is a method call. Something that might come up (but probably not) is -- what if you want to perform a normal function call on a function that's stored inside another object? Simple -- just put parentheses around the function to call: "(a.f)()" will get the field "a.f", and then call it as a normal function instead of as a method.

Finally there are supercalls, which have to do with the class and instance types and which I won't cover until later.

All types of function calls have two syntaxes that can be used: the typical C-style "algebraic" syntax, and a syntax borrowed from Haskell.

The first syntax is what you're used to; the function, followed by an optional list of arguments inside parentheses.

>>> writeln("hi!")
hi!
>>> function add(x, y) = x + y
>>> add(3, 4)
 => 7
>>>

The Haskell syntax is a bit different. Instead of an open parenthesis, you use a dollar sign ($), and there is no closing mark needed.

>>> writeln $ "hi!"
hi!
>>> add $ 3, 4
 => 7
>>>

You can nest the Haskell syntax, but keep in mind that the arguments try to go to the end of the line; so if you want to embed a Haskell call inside an expression, you'll have to use parens.

>>> writeln $ add $ 3, 4 // same as writeln(add(3, 4))
7
>>> add $ 3, add $ 4, 5 // same as add(3, add(4, 5))
 => 12
>>> add $ add $ 3, 4, 5 // watch out, this means add(add(3, 4, 5))
Error: add(2): Cannot perform the arithmetic operation 'opAdd' on a 'int' and a 'null'

>>> add $ (add $ 3, 4), 5 // this is correct, same as add(add(3, 4), 5)
 => 12
>>>

This alternative syntax is useful when writing more functionally-oriented code, where it's common to have function calls nested several deep. This syntax avoids the "pile of close-parens" that show up in these cases, while making the list of functions that are called more visible.

The Haskell syntax can be used with method calls and supercalls as well.

There is one more thing that I'll just mention now, but I won't cover until later. For normal and method calls, you can optionally specify a context parameter before the other parameters by using the 'with' keyword. It looks like "add(with x, 3, 4)", but that's all I'll say now.

Item Access Expressions

These are expressions used to access items within complex types.

Indexing is used to access items in arrays and tables, as well as for getting characters in strings. It uses the same syntax as in other C-style languages.

>>> global a = [1, 2, 3]
>>> a[0]
 => 1
>>> a[1]
 => 2
>>> a[-1]
 => 3
>>> global t = {}
>>> t[5] = 10
>>> t[5]
 => 10
>>> t["hello"] = "bye"
>>> t["hello"]
 => "bye"
>>> global s = "foobar"
>>> s[0]
 => 'f'
>>> s[1]
 => 'o'
>>> s[-1]
 => 'r'
>>>

Notice that for arrays and strings, negative indices are permitted and signify that the indexing should be performed in reverse, from the end of the object instead of from the beginning. This is a concept used throughout the libraries as well.

Field access is the act of getting or setting a named field within an object. This has different meanings for different types. For tables, field access and indexing are one and the same.

>>> t["hello"]
 => "bye"
>>> t.hello
 => "bye"
>>> t.("hello")
 => "bye"
>>>

You might be wondering what that last line, "t.("hello")" is. That's a field access, just like "t.hello". In fact, "t.hello" is just a shortcut for writing "t.("hello")". The more verbose form is more flexible, however, in that you can use any arbitrary expression that evaluates to a string as the field name. So you can put function calls, concatenations, variables -- whatever, inside those parentheses.

For types other than tables though, field access and indexing are completely separate operations. Consider namespaces:

>>> _G.a
 => [1, 2, 3]
>>> _G["a"]
Error: stdin(2): Attempting to index a value of type 'namespace'

>>>

Namespaces can only have field access performed on them; indexing them is illegal. Similarly, for user-defined types, field access and indexing can be overloaded as separate operations with separate metamethods.

Lastly there is slicing. Slicing is a way to get a subrange of a list-like object. For strings, it gets you a substring, and for arrays, it gets you a new array object which references the same data. For example:

>>> global s = "hello world!"
>>> s[0 .. 5]
 => "hello"
>>> global a = [1, 2, 3, 4, 5]
>>> a
 => [1, 2, 3, 4, 5]
>>> global a2 = a[0 .. 3]
>>> a2
 => [1, 2, 3]
>>> a2[0] = 10
>>> a2
 => [10, 2, 3]
>>> a
 => [10, 2, 3, 4, 5]
>>> a[0] = -10
>>> a2
 => [-10, 2, 3]
>>>

Notice that when we modified the values in the slice 'a2', the changes were reflected in 'a', and vice versa. However, if a or a2 are appended to or their length changed, they may move and that property may no longer hold.

Slice indices can be left out, in which case they mean "to this end". You can leave out both indices, or just write an empty pair of brackets, to mean "a slice of the whole thing". Slice indices, like with indexing, can also be negative, with the same meaning.

>>> a[.. 3]
 => [-10, 2, 3]
>>> a[3 ..]
 => [4, 5]
>>> a[..]
 => [-10, 2, 3, 4, 5]
>>> a[]
 => [-10, 2, 3, 4, 5]
>>> a[-3 ..]
 => [3, 4, 5]
>>>

Other Random Expressions

The 'as' operator is used to test whether a value derives from a certain class. This will be explained more in-depth in a later chapter.

The 'coroutine' expression is used to create thread objects and will be covered later.

The 'x.super' expression is used to get the class of an instance, the base class of a class, or the parent namespace of a namespace, and will be covered more later.

The 'in' and '!in' operators can be used to test for membership of a value in a complex object. By default, strings, arrays, and tables have semantics defined for this operator. For strings, you can only see if characters are in them. For arrays, a linear traversal will be performed and if any item in the array is identical to the searched-for value, true is returned. For tables, 'in' will look for keys that have the given value.

>>> global s = "hello world"
>>> 'w' in s
 => true
>>> 'x' in s
 => false
>>> 1 in [1, 2, 3]
 => true
>>> 5 in [1, 2, 3]
 => false
>>> 1.0 in [1, 2, 3]
 => false
>>> global t = {}
>>> t.x = true
>>> "x" in t
 => true
>>> "y" in t
 => false
>>>

The length operator (#) can be used to get, and possibly set, the length of an object. You can get the length of strings, tables, and arrays, and you can set the length of arrays. It can also be defined for user-defined types.

>>> #s
 => 11
>>> #t
 => 1
>>> global a = [1, 2, 3]
>>> #a
 => 3
>>> #a = 5
>>> a
 => [1, 2, 3, null, null]
>>>

Notice that when you set the length of an array to longer than it used to be, the uninitialized elements are set to 'null' in keeping with the rest of the language.

Basic Value Expressions

These are the simplest expressions and many don't need much explanation. String literals, numeric literals, boolean literals, character literals, and null are all expressions. There are also some basic expressions for function, class, and namespace literals that will be covered later.

Parenthesized expressions allow you to control order-of-operations, as well as to restrict the number of results that some expressions can give. For example, functions in MiniD are allowed to return multiple values. Most of the time this isn't a problem; if a function returns three values but you only assign it into one variable, that variable will get the first value and the other two will be discarded. But sometimes you only want one value, in which case you can put parentheses around the function call. In the case that the function returns no values, putting parentheses around the function call will make the result null.

>>> function f()
... {
...     return 1, 2, 3
... }
>>> f()
 => 1, 2, 3
>>> (f())
 => 1
>>>

Yield expressions are used with coroutines and will be covered later.

Vararg expressions have to do with variadic function arguments and will be covered in the functions chapter.

Member expressions will be used much more in later chapters, but it's good to demonstrate them now. When doing object-oriented programming in MiniD, to access fields or methods of the current object, you have to explicitly access them out of 'this', such as "this.x" or "this.y = 5" or "this.f()". Member expressions are a shortcut: instead of writing "this.", you can instead just write ":". So the above expressions can be equivalently written as ":x", ":y = 5" and ":f()".

The last types of expressions to discuss are array and table literals.

When you create an array, you just use brackets around a list of items.

>>> global a = [1, 2, 3]
>>> a
 => [1, 2, 3]
>>> #a
 => 3
>>>

The list of items is not actually required to be comma-separated...

>>> a = [1 2 3]
>>> a
 => [1, 2, 3]
>>>

...but you may run into trouble if you try to define an array with sub-arrays inside it and skip the commas:

>>> a = [[1 2] [3 4]]
Error: <no location available>: stdin(2:15): ']' expected; found 'Int Literal' instead

>>>

This is because the compiler tries to parse "[1 2] [3 4]" as the array [1, 2] indexed with [3, 4], or something like that. You can see it happen here:

>>> a = [[1 2] [1]]
>>> a
 => [2]
>>>

Notice that you ended up with just [2], since [1 2][1] evaluates to 2.

So, be sure to put commas between sub-arrays:

>>> a = [[1 2], [3 4]]
>>> a
 => [[1, 2], [3, 4]]
>>>

An empty pair of brackets, like "[]", will create a zero-length array. That's really all there is to array literals.

Table literals look similar to those of Lua or Squirrel, and a bit different than Python or PHP.

>>> global t = {x = 5, y = 10}
>>> t
 => {["y"] = 10, ["x"] = 5}
>>>

OK, so a couple things. First of all, the items in a table literal are "key = value" pairs. In the common case where you want string keys, the syntax in the example can be used. That brings me to the second thing: "x = 5" is just sugar for ["x"] = 5, which is how it looks when it's outputted. This bracket form is a more flexible form, since any arbitrary expression can be used as the key in that case. And the third thing is that functions can be declared inside table literals as well:

>>> t = { function f() { writeln("hi!") } }
>>> t.f()
hi!
>>>

This, again, is sugar: this is the same as writing "{ ["f"] = function f() { writeln("hi!") } }".

Just like with array literals, table literals do not require commas between their items, but again, be careful about the compiler misinterpreting what you mean:

>>> t = {["x"] = 5 ["y"] = 10}
Error: stdin(2): stdin(2:22): 'Identifier' expected; found '=' instead

>>>

Here, the compiler thinks that you want to assign "5["y"]" into the key "x". Be sure to use commas appropriately.

Array and table literals are nice, but sometimes you need more power. For that, there are comprehensions.

Comprehensions

Comprehensions are a concise way of creating arrays or tables from a loop and some conditions. They are based on set-builder notation. We'll start with array comprehensions and then demonstrate table comprehensions; they are almost the same except one builds an array and the other builds a table.

An array comprehension looks a bit like an array literal. It starts with an expression inside the brackets, which is then followed by a 'for' loop of some kind. For example, say we want an array of the numbers 0 through 9. Easy enough:

>>> [x for x in 0 .. 10]
 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

If you're familiar with set-builder notation, you can see the relation -- this would be written something like {x : x <- Z ^ x <- [0, 10]} (where <- is "member of"), although set-builder notation builds a set and not an ordered list. (note: building lists of ordered sequences of integers with arbitrary stride is more efficiently accomplished with array.range; see the array library documentation.)

How does this work? Well you can think of an array comprehension almost like a loop turned inside out. The above is virtually identical to the following:

local temp = []

for(x: 0 .. 10)
    temp.append(x)

except that this snippet of code can't evaluate to a value, whereas an array comprehension can.

The previous example used a numeric for loop; you can also use generic for loops:

>>> global a = [1, 2, 3, 4]
>>> [x * 2 for x in a]
 => [2, 4, 6, 8]
>>>

You can also nest loops:

>>> [[x, y] for x in 0 .. 3 for y in 0 .. 3]
 => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]
>>>

And add conditions:

>>> a = [3, 6, 7, 10]
>>> [x for x in 1 .. 11 if x in a]
 => [3, 6, 7, 10]
>>> [x for x in 1 .. 11 if x !in a]
 => [1, 2, 4, 5, 8, 9]
>>>

Remember that the clauses in the comprehension are nested left-to-right; so the above is something like:

for(x: 1 .. 11)
    if(x !in a)
        temp.append(x)

Now for table comprehensions. They are very similar, except that instead of just values, you need keys and values. A common operation is, given a mapping from one set of values to another, to find the reverse mapping -- from the values to the keys. A table comprehension makes it easy.

>>> global t = {x = 5, y = 10, z = 15}
>>> {[v] = k for k, v in t}
 => {[5] = "x", [10] = "y", [15] = "z"}
>>>

Notice the form: a bracket-style key-value pair followed by comprehension clauses.

Note that table comprehensions do not ensure that the keys are unique:

>>> t.x = 0
>>> t.y = 0
>>> t.z = 0
>>> t
 => {["y"] = 0, ["z"] = 0, ["x"] = 0}
>>> {[v] = k for k, v in t}
 => {[0] = "x"}
>>>

So if you have multiple values with the same key, they will overwrite one another.

That's it

That's all for expressions. The next chapter is about statements.