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:

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:

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