Hush aims to be a pure object-oriented language with a clean syntax. It supports the following features:
Objects respond to method calls. A method call expression has the receiver on the left and the method name with arguments following.
Method names contain space separated words beginning with a lower case letter. To include arguments in the method call, you place a colon ':' after one of the words:
x = 2 -- set x to the object 2 x sqrt -- method call "sqrt" to x x pow:0.5 -- method call "pow:" to x with argument 0.5 x mulBy:2 -- method call "mulBy:" to x with argument 2
Method calls can be as many words as you want, with arguments given in any place:
canvas put pixel x:1 y:2 col:3 -- canvas is sent "put,pixel,x:y:col:" -- with arguments 1 and 2 and 3 foo bar baz:1 and car:2 -- foo is sent "bar,baz:and,car:" -- with arguments 1 and 2
Operators are just method calls with 1 argument and you can define you own methods for operators just like normal. Operators also have an associated binding power and associativity so that standard mathematical expressions give the expected results:
a = b * c + d / e -- equivalent to (b * c) + (d / e)
Enumerations and constants are supported in Hush. They both take the same form as a class name then a forward tick then a lower case name:
Math'pi -- constant pi State'idle -- enumeration
Defining enumerations and constants is very easy:
class Math constant pi = 3.1415926 class State enumerate idle, forward, reverse
Methods can be hidden from other classes with the 'hidden' keyword. These methods can only be accessed by a method call from an object of the same class.
class MyClass hidden myOwnMethod is ...
Inner classes can also be hidden:
class MyOuterClass hidden class MyInnerClass ...
Inheritance takes on a more general form with the use of a special member object. You can think of an object as a linked list of instances, with a method call beginning at the leaf and propagating to the root until a match is found. This inheritance chain is fully dynamic and you can change the base of an object at run-time.
class MyClass is BaseClass ... changeBaseTo:newBase is base = newBase
Dynamic bases also permit the use of mixin classes: wrapper classes which add functionality to an object. Say you wrote a general sort method which could operate on any object which responded to "at:" and "at:put:" (an Array for example). Then you could add this new sort method to any object just by wrapping or mixing the instance.
class Sort is Object single < > obj is -- mixin operator ^ new base:obj -- wrap object in new Sort instance sort is -- general sort method ... class SomeProgram someMethod is Sort < > Array lo:1 up:10 -- mixin Sort to Array instance ...
The two short circuit operators 'andThen' and 'orElse' behave correctly when it comes to local variable creation. Only the variables guaranteed to be initialised are accessible within the respective blocks of a conditional expression:
if (x = 1 < 2) andThen (y = 2 > 3) then -- both x and y are valid here else -- only x is valid here because of short circuit behaviour -- neither x or y are valid outside the block
Of course, if the variables are created outside the block then they are accessible everywhere.
Any expression can be followed by an 'except' block:
file = File open:"foo.txt" except FileIOError do "can't open file foo.txt" println
You can use the 'do' expression to provide an exception handler for a block of expressions:
do file = File open:"foo.txt" data = file read bytes:42 file close except FileIOError do "there was an error" println
Any object can be raised as an exception with the 'raise' keyword. You can also store the raised object to use later:
do foo bar raise 123 except e is Int do e println
An unbound method is a method call without an object to receive that call. This method can be stored for later application to one or more objects. To create an unbound method, just place a hash symbol '#' in place of the receiver:
meth = # println meth apply to:"hello world"
This code will first create a method call 'println' and store it in the object 'meth'. Then the method call is applied to the string "hello world". The end result is that "hello world" is sent the method call 'println' and it prints itself out.
Unbound methods provide a neat way of applying the same method call to a list or array in a similar fashion to functional mapping of lists:
-- pictures is a list of objects which can be draw to a Canvas canvas = Canvas width:320 height:240 meth = # draw to:canvas pictures apply:meth
A closure is created with square brackets, and evaluated with the 'evaluate' method call:
closure = ["hello world"] println closure evaluate
Closures can also take arguments:
sq = [:x ^ x*x] result = sq evaluate with:3 result println
In this example, 'x' is the argument and is bound to the value '3' when the closure is evaluated. The result will then print the value '9'.