Haxe Code Cookbook
Haxe programming cookbookPrinciplesEverything is an expression

Everything is an expression

Reading time: 1.5 minute

Many programming languages split code into two kinds of elements: statements and expressions. Statements perform some action (e.g. if/else) and expressions return values (e.g. a + b).

This is NOT the case in Haxe. In Haxe, everything is an expression which means it can be used where value is expected.

Examples

if/else is an expression returning value of either true or false branch:

trace(if (Math.random() > 0.5) "Hello" else "Bye");

try/catch is an expression returning value of try if everything is okay or catch if error is caught:

trace(try haxe.Json.parse("{") catch (e:Dynamic) null);

switch is an expression returning value of the matched case (or default):

trace(switch (Std.random(3)) {
  case 0: "zero";
  case 1: "one";
  case 2: "two";
  default: "impossible!";
});

Even blocks are just expressions, returning value of the last of their expressions:

trace({
  var l = new List();
  for (i in 0...10) l.add(i);
  l;
});

Actually, knowing that blocks are mere expressions, they are not necessarily required for e.g. functions so this is a perfectly valid function definition:

function toInt(s:String):Int return Std.parseInt(s);

Some expressions, such as loops or var declarations don't make any sense as values, so they will be typed as Void and thus won't be able to be used where value is expected. For example the following won't compile:

trace(for (i in 0...10) i); // ERROR: Cannot use Void as value

Some expressions such as throw, continue or return also don't make sense as values, however they are allowed to be in value places so code like the following is possible:

for (file in ["a.txt", "b.txt", "c.txt"]) {
  // try reading file or skip the loop iteration
  var content = try sys.io.File.getContent(file) catch (e:Dynamic) continue;
  trace(content);
}

One interesting feature of the return expression is that it can be used with a non-empty expression for returning out of Void functions. While it can be confusing at first, it is very pragmatic when dealing with callback-based code. This kind of expressions will be transformed from return someVoid(); to someVoid(); return;. For example:

function getContent(fileName:String, callback:String->Void):Void {
  if (fileName == "")
    return callback("New file"); // invoke callback and return early if `fileName` is empty string
  
  var content =
    try
      sys.io.File.getContent(fileName)
    catch (e:Dynamic)
      return callback("ERROR: " + e); // invoke callback and return early in case of error

  callback(content.toUpperCase()); // invoke callback normally
}

Contributors:
Last modified:
Created:
Category:  Principles