Home‎ > ‎

Highlights

Support multiple programming paradigms

Zimbu does not enforce one specific style of programming.  You can do it they way that works best for the task at hand.

Procedural, like C: define procedures and data structures separately.
Object Oriented, like Java: define classes that hold both procedures and data, support inheritance and other mechanisms to encourage code re-use.
Functional: support function references, closures and callbacks.
 

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 (like the comment that many add in C programs).

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.

Compiler plugins

Quite often you don't write code manually, but  generate it from a template or an interface specification.
Or you want to import an image file into your code as a literal.  Zimbu supports this which plugins.  For example, to use Google protocol buffers:

   IMPORT.PROTO zui.proto

The proto plugin will read the "zui.proto" file and generate classes for the protocol messages it defines.
These classes can then easily be used in the Zimbu code.
You can write your own plugins and extend the compiler in a flexible way.

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.C a = NEW()          # a can only be an instance of Animal, not Fish. 
  Fish     f = NEW()
  Animal   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 for it.

  CLASS Bear IMPLEMENTS Animal.I   # Explicitly implements Animal.I
    ...
  }

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().

Destructors and other ways to free resources

When an object is about to be garbage collected and it has a Finish() method, that method is invoked.  It can free up resources and do anything needed. Destructors are also called on exit, thus they are much more useful than Java destructors. Details can be found in the language specification, Object Destruction section.

Combine this with the feature to put an object on the stack, and the destructor will be called when leaving the block where the variable was defined.  Also when an exception is thrown while executing statements in the block. An ideal mechanism to grab a lock and make 100% sure it is released at the end of the block, as the new autoLock class will do:

  PROC addFoo(Foo f)
    autoLock %lock = NEW(myLock)  # will grab myLock
    myList.add(f)  # This might crash
    # autoLock.Finish() called, which releases myLock
, no matter what
  }

On top of that there is the DEFER statement (borrowed from go). It is a convenient way to free up resources in many situations.  See the "Cleaning up after yourself" page.

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.
Comments