Overview
Copper is a simple imperative language statically typed with type inference and genericity.
What it looks like:
struct Point
attr x : Int32
attr y : Int32
method initialize (x, y)
self x = x
self y = y
return self
method translate (dx, dy)
self x += dx
self y += dy
function main
var pt = {Point} initialize (3, 5)
pt translate (1, 0)
Structures, methods and functions have no end delimiters. It works without significant indentation, it is not required as elements can not be nested:
- Starting a new method terminates automatically the previous one.
- The function terminates the previous structure definition.
Multiple Return Values
function sort (x, y)
if x > y
return y, x
else
return x, y
end
function main
var min, max = sort (51, 15)
Returning multiple values is often useful to return an error code and a value:
var error, file = open (...)
This way you don't need to use a special value to say that there is an error and retrieving the error in a second call forcing the callee to store a state.
Another advantage is to swap values, no need to use temporary variable:
x, y = y, x
OOP Helpers
The language is not an oriented object language but it provides some features to facilitate object oriented designs.
Syntactic sugar:
message (obj, arg1, arg2, arg3)is strictly equivalent to:
obj message (arg1, arg2, arg3)
Contextual functions with an implicit arguments in structures:
struct Point
attr x : Int32
attr y : Int32
method initialize (x, y)
self x = x
self y = y
It creates a function initialize (self, x, y) that applies only when the first argument is a pointer to a Point.
Blocks
A block is an additional argument passed to a function. The function can evaluate this block using the yield statement.
// Iterates between start and limit
function range (start, limit)
var i = start
var n = limit
while i <= n
yield i // Call the block with i as parameter
i ++
end
function main
var sum = 0
// Call the range function with start, limit and the block
range (1, 10) do x
sum += x
end
return sum
Blocks provides a better abstraction: they allow to enumerate sequence's elements without knowing the implementation. It also reduces the code: an enumeration is done with one operation instead of the three usual steps (initialization, test end of iteration, advance to next element).
This system is very simple and flexible:
- Very concise syntax
range (1, 10) do x ... end - Not limited to a single variable
dictionary eachKeyAndValue do key, value list eachIndexAndValue do index, value
- It is not limited to iterators:
function readFile (name) var file = fopen (name, 'r') if file notNil yield handle file fclose end function main "dummy.txt" readFile do file // Use file here end - An object can have multiple iterators
file eachLine do line file eachChar do char
Genericity
A limited genericity is implemented.
Optional Argument Types
The type is optional when declaring the arguments of a function.
function max (x, y)
if x > y
return x
else
return y
end
This function will work for any type of x and y as long as x > y is valid (the operators can be overloaded).
Parametric Structures
Structures can have parameters:
struct Vector (T)
attr items : *[] T // Pointer to an array of T
attr size : Size
...
Variadic arguments
Functions can have a variable number of arguments.
function sum(...)
var sum = 0
each_extra do x
sum += x
end
return sum
Passing variadic arguments to another functions is really easy, the three dots represent a tuple:
function sum_plus_3(...)
return sum(1, ..., 2)
Variadic functions without type is very useful:
function in (x, ...)
each_extra do value
if x == value
return true
end
end
return false
This function works with any type. Code will be generated for each different number and size of arguments.
The in function is a very interesting case as LLVM can optimize it very smartly:
if x in (1, 2, 5)
will first first translated by Copper with a function like this:
function in (x:Int32, a1: Int32, a2: Int32, a3: Int32)
if x == a1
return true
end
if x == a2
return true
end
if x == a3
return true
end
return false
and it will be then fully inlined and optimized by LLVM to something like this:
if bit_test (0b100110, x)
i.e. testing the bit number x of 0b100110 which is done with a single machine instruction.
Order of Definitions Is Not Significant
No need of forward declarations, you can group definitions logically, not in an order forced by the limitations of the compiler.
Other Differences with Zinc
- No more space in identifiers. In fact this feature can be re-introduced by changing ~100 lines of code.
- No more := trick: the compiler is smart enough to detect when a variable is never re-assigned.
- No unions.
- Never specify the return type of recursive functions.
- No more local trick. The pointer syntax is regular (similar to C but reversed).
- Types are in the same name space as the other identifiers, so the type names have to be distinct from function names, variable names or enumeration values. The convention is to start types with an upper case letter.