CHAPTER 14

Functional Programming

In early chapter we briefly talked about how to define and call functions. That is, however, only a tiny part of function in Julian. Now we are unveiling its full power, showing how Julian is essentially a functional language as well.

First-class functions

Julian's function is actually a special class type. There are some distinct features to set them apart from other classes, such as:

  • It cannot be extended with class definition
  • It cannot be instantiated with new operator
  • It supports the function call operator

Otherwise, function is pretty much like plain classes. The function instance can be assigned, stored and passed around. For each function, a child class of Function is defined, but Julian doesn't perform type compatibility checks among these subclasses. Throughout the code there is only one type, Function, that is used during declaration. Passing a Function of different signature is allowed, but may eventually fail at the callsite.

Assign function to a variable:

  int triple(int a){
    return a * 3;
  }
  
  Function f = triple;
  int k = f(2); // k = 6

Use Function as parameter:

  void proxy(Function f, var value){
    f(value);
  }
  
  void print(string s){
    Console.println(s);
  }
  
  proxy(print, "hello!"); // hello!

In example above, we happened to pass along a function with a compatible signature. If we hadn't, it would have incurred runtime exception.

Similarly, methods of class are also first-class instances. In particular, the instance method carries the instance to which it belongs, so it is essentially a closure.

  class Car {
    private int speed;
    accelerate(int arg){
      this.speed += arg;
    }
    getSpeed(){
      return speed;
    }
  }
  
  Car c = new Car();
  Function handle = c.accelerate;
  handle(5);
  int s = c.getSpeed(); // 5

The capability of referencing to method members brings a minor problem: how does it handle overloaded methods? In fact, if the method of certain name is overloaded, referencing to that name will get back a special object called MethodGroup, which is a built-in subclass of Function. When you invoke a method group, the same matching algorithm that is performed during plain method calls will be applied, so that the first method that matches the arguments will be invoked.

  class Car {
    accelerate(int arg){
    }
    accelerate(){
      accelerate(5);
    }
    ... ...
  }
  
  Car c = new Car();
  Function handle = c.accelerate;
  handle();
  handle(20);
  int s = c.getSpeed(); // 25

Lambda

Using functions as first-class object is great experience but the limitation to it is also draconian. Most frustratingly you cannot build a function in line. To complement this, Julian allows you to define lambda as an expression. This gives programmer the maximum flexibility on when to define a function, and its arbitrary location means it can refer to a very dynamic context.

To define a lambda, use => operator, which splits the signature and lambda body. The signature contains a list of parameters, but for each you may only give a name, with the type unspecified. If the type is not given, Any will be used. The body part can contain one or more lines. If it contains one line, the curly brackets are not needed.

  Function f = (v) => {
    if(v == 10){
      v = "a";
    } else if(v == "a"){
      v = 10;
    }
    return v;
  };

  f(12);

Some more examples:

  // single-line body: a return statement
  var l1 = (int x) => return x + 5;
  // single-line body: a throw statement
  var l2 = (string s) => throw new Exception(s);
  // single-line body: an expression. This has same effect as return statement
  var l3 = (string s) => s + s;
  // single argument, untyped
  var l4 = x => { return x + 5; };
  // multiple arguments, untyped
  var l5 = (x, y) => { return x + y; };

These examples only show how to define a lambda and assign it to another variable. But you can do more with it. In particular, you can create a function to return. By returning a function out of the current frame, it's said the function escapes. Escaping function works by defining a closure around it so that even if we are outside the original frame, all the references remain valid. Julian creates a closure by reference-copy at the time the lambda is created.

  Function create(){
    int c = 5;
    f = (int x) => { return x + c; };
  }
  
  int a = create()(3); // 8

In the example above, when we define the lambda within function f, the entire local context is copied to the lambda object's closure. The copy is per-reference, so the lambda can keep changing these value if it's outside the original scope. This can be illustrated more clearly with the following example:

Function getFunc(int i){
  int c = 50;
  return (int x) => { // When this is interpreted, the current frame is copied to
                       // closure, which includes c(=50) and i(=10)
    c = c * 2;
    return x + i + c;
  };
}

Function f = getFunc(10);

int a = f(1); // 111 - the copied c is now increased to 100
int b = f(1); // 211 - the copied c is now increased to 200

The function we obtained from calling getFunc(10) maintains a closure around it. This closure contains a reference to local variable c and argument i which were present on the stack when the function was created. That frame has ever since gone, but these two values are still being pinned by the closure and thus won't be subject to garbage collection. Of course, if we call getFunc() again it will create new instance of c and i to be referred by the new lambda. This new closure will not be related to the previous one in any way.

In a more classical example, let's see how currying is achieved:

  int add(int a, int b) {
    return a + b;
  }

  Function addX(int x) {
    return a => add(a, x); // Curry add(a, b)
  }

  Function add7 = addX(7);

  int c = add7(11); // 18

It should be noted that closure is a different concept than context. A lambda copies the local frame to closure so that it can keep referencing to these on-stack values, but its context is larger than that. For lambdas created inside instance methods, they have access to the current object through this; for lambdas created inside global functions, they can refer to global variables just like global functions. This referential capability is further propagated to lambdas created by these lambdas themselves.

  class Person {
    string name;
    Function getName(){
      return (string name) => {
        this.name = name;
      };
   }
  }

  Person p = new Person();
  Function f = p.getName();

  f("b");

  string x = p.name; // "b"

Dynamic invoke

The last feature about functional programming in Julian is dynamic invoke, which allows the programmer to bypass type checking to some extent. To invoke a method dynamically, call invoke() on the Function object, instead of using call-syntax directly.

  string combine(string s1, string s2){
    return s1 + s2;
  }

  string s1 = combine.invoke("a", "b"); // ab

The power of dynamic invoke is that you don't have to provide same number of arguments as parameters. If your arguments come short of expected, the remaining parameters will be initialized with default values. If your feed excessive arguments, the extra will be ignored.

  string combine(string s1, string s2){
    return s1 + s2;
  }

  string r1 = combine.invoke("a"); // "anull"
  string r2 = combine.invoke("a", "b", "c"); // "ab"

Also, if the function doesn't have return value (declared as void), dynamic invoke will return null.