Fan

 

Functions

Overview

Functions are first class objects in Fan modeled via the sys::Func class. Functions have a signature formally defined as a set of parameter types and a return type. Functions which don't return an object have a Void return type. Functions are created one of three ways:

  1. Methods: all methods are really wrappers for a function
  2. Closures: closures are expressions which result in a function
  3. Curry: the & curry operator is used to create a new function by binding arguments to an existing function

Function Signatures

The Func class is a built-in generic type, with a custom parameterization syntax:

// format
|A a, B b ... H h -> R|

// examples
|Int a, Int b->Str|  // function which takes two Int args and returns a Str
|Int, Int->Str|      // same as above omitting parameter names
|->Bool|             // function which takes zero args and returns Bool
|Str s->Void|        // function which takes one Str arg and returns void
|Str s|              // same as above, omitting optional void return
|->Void|             // function which takes no arguments and returns void
|,|                  // shortcut for above

The examples above are type signatures much like you'd use Str or Str[].

Calling Functions

Functions are just objects like everything else in Fan. All functions subclass the Func class which provides methods for invoking the function. The most basic method is Func.callList which takes a list of arguments and returns the result (or null if the return if void):

// func is a function which takes two Int args and returns an Int
|Int a, Int b->Int| func
func.callList([7, 8])

The Func class also supports the call method for calling with different arity (zero through eight). For example to call the function above with two arguments:

func.call(7, 8)

Using the call arity versions provides better performance in most cases because it skips packaging up the arguments in a list.

Fan also supports a bit of syntax sugar to call a function like a normal method call using the () operator. For example we could call the function above using this syntax:

func(7, 8)    // syntax sugar for func.call(7, 8)

Type Compatibility

Functions have some special rules when it comes to type compatibility. The axiom for type compatibility is that type A is compatible for type B if A can be used whenever B is expected. Most of the time this means A extends from B through inheritance. For example Int is type compatible with Num because anyplace Num is expected, we can pass an Int.

A type declaration for a function means "these are the are the arguments I'm going to pass to this function and the result I expect back". So function type A is compatible with function type B if A can accept the arguments which B declares it is going to pass and returns an expected type. Specifically, function type A is compatible with function type B if these rules apply:

  1. A declares the same number or less parameters than B
  2. Each parameter type in A is compatible with its respective parameter type in B
  3. A returns a type compatible with B (or if B returns void, then A can return anything)

The following table illustrates some examples which shows what Type.fits would report:

Num  fits  Int  =>  false
Int  fits  Num  =>  true

|Int a|  fits  |Int a|  =>  true
|Num a|  fits  |Int a|  =>  true
|Int a|  fits  |Num a|  =>  false

|Int a|  fits  |Int a, Int b|  =>  true
|Int a, Int b|  fits  |Int a|  =>  false

|->Void|  fits  |->Int|    =>  false
|->Int|   fits  |->Void|   =>  true
|->Int|   fits  |->Num|    =>  true
|->Num|   fits  |->Int|    =>  false

The first two items in the table above are for reference - Int fits a Num, but not vise versa. Next let's look closely at this example:

|Num a|  fits  |Int a| =>  true

What this shows is that if a function type is declared to take an Int, we can pass a function that accepts a Num. That may seem counterintuitive at first, but remember that functions are the flip side of normal type checking. Here is a concrete example of that concept in terms a typical Java or C# programmer might find more natural:

class WidgetEvent {}
class ButtonEvent : WidgetEvent {}
addButtonListener(|ButtonEvent evt| callback)

In the code above ButtonEvent is a subclass of WidgetEvent. We've got a method which registers a callback to invoke when a button is pressed - the argument passed to the callback will be a ButtonEvent. However, if we happen to have a function that accepts any WidgetEvent, then it will quite happily accept ButtonEvent arguments:

static void anyWidgetCallback(WidgetEvent evt) { ... }

button.addButtonListener(&anyWidgetCallback)

This is what is meant by functions being the "flip side" of normal type checking. Where normal type checking accepts any specialization of a type, function type checking accepts any generalization of a function.

Arity Compatibility

Next let's look at how arity (number of parameters) figures into functional type compatibility by dissecting these examples:

|Int a|  fits  |Int a, Int b|  =>  true
|Int a, Int b|  fits  |Int a|  =>  false

Here we see that a function that accepts one Int is compatible with a function type that generates two Ints. This is an ability of all functions in Fan - to accept more arguments than they will use. It is kind of like default parameters in reverse. We use this technique all the time in the core classes. For example the Map.each method is used to iterate the key/value pairs:

// actual signature of Map.each
Void each(|V value, K key| c)

// iterate with function that only accepts value
map.each |Obj value| { echo(value) }

// or iterate with function that accepts both value and key
map.each |Obj value, Obj key| { echo("$key = $value") }

Many of the APIs which accept a function will pass multiple parameters, but you don't actually have to use all of those parameters.

Methods

In Fan, all methods wrap a function accessed via the Method.func method. The Func for a method serves as its reflective handle. This relationship between functions and methods is a key aspect of how Fan bridges object oriented and functional programming (the flip side is that all functions are an object).

Mapping static methods to functions is straight forward:

static Int add(Int a, Int b) { return a + b }

func := type.method("add").func
nine := func(7, 2)

One gotcha to be aware of - you can't access the Method.func method without parenthesis, and then use the parenthesis to invoke the function because the parenthesis will bind to the Method.func call:

type.method("add").func               // returns Func
type.method("add").func()             // same as above

type.method("add").func().call(7,2)   // invoke function
type.method("add").func()(7,2)        // same as above

Instance methods map to a function where the first argument is the implicit this parameter. If you've ever used Python this concept is pretty much in your face with the explicit self argument. Fan lets you use instance methods like Java or C#, but we still need to map those OO methods to functions. Let's consider this example:

m := Str#replace
f := m.func

// note method params does not include the
// implicit this, but the function params does
m.params  => [sys::Str from, sys::Str to]
f.params  => [sys::Str this, sys::Str from, sys::Str to]

// both of these result in "hello!"
s1 := "hi!".replace("hi", "hello")
s2 := f("hi!", "hi", "hello")

The code above gets the Str.replace instance method as a function. The replace method takes two string arguments, but when flattened into a function it takes three string arguments because we have to account for the implicit this argument.

Currying

Fan uses the unary & operator to perform a functional curry. Currying is the process of binding one or more fixed arguments to a function to create a new function. It is also known as a partial application (see note below).

The curry operator binds whatever parameters are specified including the implicit this. Any remaining unbound parameters become parameters of the resulting function:

f := |Int a, Int b, Int c->Str| { return "$a $b $c" }
g := &f(0)
h := &f(0, 1)
i := &h(2)

f(0, 1, 2)  =>  "0 1 2"
g(1, 2)     =>  "0 1 2"
h(2)        =>  "0 1 2"
i()         =>  "0 1 2"

The curry operator is often used as a mechanism to get a method as a function. Unlike using reflection it lets the compiler perform type checking, and results in more efficient code. Consider a typical example to register callback functions:

Void onPress(|,| callback) {...}

Void pressed(Str what) { echo(what) }

ok.onPress(&pressed("ok"))
cancel.onPress(&pressed("cancel"))

This example illustrates use of the curry operator to create functions with fixed arguments bound to the pressed method. In both cases we are binding the implicit this argument, plus a Str argument.

The difference between a closure and a curry is that a closure binds a variable and a curry binds a reference to value:

x := "alpha"
f := |->Str| { return x }
g := |Str v->Str| { return v }
h := &g(x)

f()  =>  "alpha"
h()  =>  "alpha"

x = "beta"
f()  =>  "beta"
h()  =>  "alpha"

Note: functional programming gurus don't consider partial application the same thing as currying. But the distinction seems be sufficiently muddied that Fan stuck with the term curry for this feature.

Immutable Functions

An immutable function is one proven to be thread safe. Immutability is determined by the compiler. You can check immutability at runtime via sys::Obj.isImmutable. Immutable functions are often required when working with actors.

The compiler marks functions as immutable based on the following analysis:

  • static methods are always automatically immutable
  • instance methods on a const class are immutable
  • instance methods on a non-const class are never immutable
  • closures which don't capture any variables from their scope are automatically immutable
  • curried functions which only capture const variables from their scope are automatically immutable