Home‎ > ‎

Highlights

Less punctuation

Many C-style languages contain a lot of punctuation. Not only is it easy to get this wrong, it also obscures the core of the code.

int myfunc(int a, int b) {
  int result = 0;
  for (int i = a; i <= b; ++i) {
    result += i;
  }
  return result;
}

Zimbu does away with most punctuation:

FUNC myfunc(int a, int b) int
  int result = 0
  FOR i IN a TO b
    result += i
  }
  RETURN result
}

Using } to end a block without a { to start it is a trademark of Zimbu. Perhaps it would be more logical to use END, but it turns out that using } makes the code easier to read.

Zimbu encourages using one statement per line.  In special cases, e.g., a long list of similar lines, Zimbu allows using a semicolon to separate two statements:

CASE 4; a = 3; b = 6
CASE 5; a = 4; b = 8

BREAK is not needed, that is the default behavior. PROCEED can be used to fall through.

Type inference

In Java you often see a line like this:

SomeClass<SomeType> foo = new SomeClass<SomeType>();

Having to specify the type twice is bad.  Zimbu has two ways of doing this:

SomeClass<SomeType> foo = NEW()
VAR foo = SomeClass<SomeType>.NEW()

The first way is preferred, declaring the type of var and instantiating an object of that type.
The second way is more suited for when the object is created in another way, where you do not need to know the type, e.g.:

VAR foo = Foo.createFromBar(bar)

When using VAR the type of the variable will be set at the first assignment. All use after that must match that type. To have a variable that can be any type, with runtime type checking, use ANY (this has not been implemented yet).


Easy to read

Programs are read many times more often than written. And it must be easy to spot mistakes. For example in large numbers:

    n = 1'000'000'000
    m = 0x12a'f3e7'3b34
    x = 0.000'000'01

Imports

When using functionality from standard Zimbu libraries there is no need to import anything.  The compiler knows where the libraries are.

Some languages have the problem that you never know what you get from an import. Especially in C and C++ it is possible to break a working program by adding an item in a header file.
In Zimbu an import defines exactly one symbol and it matches the file name:

   IMPORT "Hello.zu"                  # Defines Hello and nothing else
   IMPORT "new/Hello.zu" AS NewHello  # Imports Hello as NewHello

The imported symbol can represent a module, class or something else.

Clarity

In most Object Oriented languages a class name can be used as a type that can also be a child object of that class. What this actually means is that the variable can store an object that implements the interface of the class.  In Zimbu it is possible to really say that a variable can only be an object of a class and not a child class.

  CLASS Animal                # Also defines Animal.I, the interface of Animal
    ...
  }
  CLASS Fish EXTENDS Animal   # Automatically implements Animal.I
    ...
  }
  Animal   a = NEW()          # a can only be an instance of Animal. 
  Fish     f = NEW()
  Animal.I ai = f             # ai can be any object that implements Animal.I
  a = f                       # Error!  A Fish is not an Animal, it's a kind of Animal

The .I can be added to any class, thus it is easy to create a class that implements the interface of any other class, without specifically defining an interface.


Threads

Starting a thread to execute a method:

  PROC sayHello()
    
IO.writeLine("Hello one")
    
TIME.sleepSec(1)
    
IO.writeLine("Hello two")
  
}
  
thread t = NEW(sayHello)
  t.start()
  IO.writeLine("Waiting for the thread to finish")
  t.wait()
  
IO.writeLine("Thread finished")

"thread" is the class for controlling a thread that executes a method. The NEW() method takes an argument that is of type "proc<>", a method without an argument that doesn't return anything.

More information about the thread class is in the documentation.

Pipes

A pipe can be used to communicate between threads.  It takes care of synchronization and locks.  Example:

   VAR pipe = THREAD.eval({ => "hello from the thread!" })
   # do something else
   IO.writeLine("The thread says: " .. pipe.read())


This creates an "evalThread" object to evaluate a lambda function and returns the pipe from which the result can be read.

To get access to the evalThread it can be split up into parts:


    evalThread<string> t = NEW()
    pipe<string> tp = t.eval({ => "hello thread!" })


Although you can't see it, the Zimbu compiler will check the type of the pipe elements.  It sees that the lambda function returns a string, creates "pipe" with the type "pipe<string>".  And it knows that "pipe.read()" will then return a string, which is OK for IO.writeLine().

BITS

In C one often defines flags and masks to be able to put several small values into one number.  This is efficient and makes it easy to pass the data around. In Zimbu this is done with a BITS:

BITS MyFlags
    bool  $done   # boolean flag takes 1 bit
    Kolor $color  # enum with 3 values takes 2 bits
    int5  $count  # 5 bit integer
}
ENUM Kolor
    red
    green
    blue
}
MyFlags foo = color=red + count=3
IF foo.color == Kolor.red
   setRed(foo.count)
   foo.done = TRUE
}

A BITS value is passed around as a number, but the fields can be accessed by name. Similar to how in C one would use #define a mask for each field to get out the right bits. In Zimbu this is just as efficient plus does type checking and defines the field names local to the BITS instead of the #define that is visible in the whole file.

Getters and setters

Some argue that direct access to members must not be used, because some day you may want to check the new value or lazily compute the value, and that would require changing all the users of the class.  However, the result is a lot of boiler plate code that is added "just in case".  With Zimbu getters and setters can be added later without changing the interface of the class:
 
SomeType $member
  GET() VAR                        # used when getting $member
     $updateMember()               # lazy updating
     RETURN $member
  }
  GET() OtherType
     $updateMember()               # lazy updating
     RETURN $member.toOtherType()  # type conversion
  }
  SET(VAR st)                      # used when setting $member
     CHECK.notNull(st) 
     $member = st
  }
  SET(OtherType ot)
     $member = NEW(ot)  # type conversion
  }

This also illustrates automatic conversion when assigning a value of a different type. VAR is used for the original type.