Exit Full View

Feather / src / dist / documentation / FunctionTypes.md

Functions Types

Functions are first class citizens in Feather. This means you can treat a function like any other piece of data. You can assign it to variables, and pass them to other functions.

Note, this is NOT the same thing as lambdas. Feather doesn't support lambdas, whereas Java doesn't support function types.

Syntax

When we declare a local variable, we can specify its type, such as int, String etc. So what is the type of a function?

Here's an example for functions which take a String, and return an int :

(String)->int

In general the syntax is :

(ArgType1,ArgType2...ArgTypeN)->ReturnType

If the function doesn't return anything, then it is just :

(ArgType1,ArgType2...ArgTypeN)

So a function which has no parameters, and doesn't return anything is just :

()

Okay, so we know how to declare a function's type, but how do we assign a function to a local variable?

class Greetings {
    static fun english( name : String ) {
        println( "Hello $name. How are you?" )
    }
    static fun french( name : String ) {
        println( "Bonjour $name. Ca va?" )
    }
}

val greet = Greetings::english

We can now call greet as normal :

greet( "Nick" )

We can also use functions from Java libraries (Java folks would call them static methods, not functions) :

import static uk.co.nickthecoder.feather.runtime.Extensions

val caseConverter = ::toUpperCase

Non-Static Functions

In the example above, Greetings had static methods. What if they were non-static? Now are greetings can be directed to any PrintWriter

class Greetings( val out : PrintWriter ) {
    fun english( name : String ) {
        out.println( "Hello $name. How are you?" )
    }
    fun french( name : String ) {
        out.println( "Bonjour $name. Ca va?" )
    }
}

val greet = Greetings::english

So far so good, but if we try to call greet now it will fail!

greet( "Nick" )

That's because we haven't told it which instance of Greetings to use.

val greetings = Greetings( System.out )
val greet = Greetings::english
greet( greetings, "Nick" )

That's not nice, I wanted greet to have only 1 parameter. We can do it like so :

val greet = Greetings( System.out ) :> english
greet( "Nick" )

The new syntax :> is saying : take the thing on the left, find a method called english and return a Funtion type, but when we call it, always use the thing on the left as the first parameter. That's called Currying see below.

Explicit Parameter Types

If there are 2 or more functions with the same name (with different parameter types), then you will need to specify the parameter types. e.g.

class Foo {
    static fun bar( n : float ) {}
    static fun bar( i : int ) {}
}

val myFunc = Foo::bar<int>  // Ensure we get right bar method, by including the parater type int

If we omitted the <int> we would get an error from the compiler, because it cannot guess which version of bar you intended.

Currying

If you've never heard of currying, I suggest you google it, I'm sure others will describe it better than I can.

Here's a contrived example. Suppose we wish to upper or lower case a single character in a string

fun partlyToUpper( index: int, original : String ) : String {
    // Implementation omitted! Not as simple as you'd imaging.
    // What is the upper case of this character german letter: ß? SS?
}
fun partlyToLower( index: int, original: String ) : String {
    ...
}

Now imaging we want one chuck of code which needs change case of the nth character of a strings it encounters. We could implement it (badly) like so :

fun doSomething( int index, transformation : (int,String)->String ) {
    for ( str in someCollection ) {
        val result = transformation( index, str )
    }
}

doSomething( 3, ::partlyToUpper )

We could implement the same thing, by passing a function, with the index curried :

fun doSomething( transformationr : (String)->String ) {
    for ( str in someCollection ) {
        println( transformation( str ) )
    }
}

doSomething( ::partlyToUpper.curry( 3 ) )

Note that in the curried version doSomething doesn't need to know that the transformation requires an int. This not only makes it more readable, but also more general purpose.

When non-static methods are used as parameters, the first argument is always the receiving object, so it is the receiver that can be curried :

class Foo {
    fun blah( i: int, b: float ) {
        ...
    }
}
 
// We don't need to explicitly declare the type, I've only included it to
// show you that its type is (int,float). Only the receiver of type Foo
// has been curried.
val myFunc : (int,float) = Foo::blah(int, float).curry( Foo() )

That's a little long-winded, so there is a terser way to achieve the same thing :

val myFunc = Foo() :> blah

More Curry

We can curry a function more than once (assuming the function has at least 2 parameters)

val myFunc : (int,float) = Foo::blah(int,float).curry( Foo() )
val moreCurry1 = myFunc(3)
// And here's another way to achieve the same result :
val moreCurry2 = Foo::blah.curry( Foo() ).curry( 3 )

Why?

Why treat functions as data? There's a good chance you've never done so, especially if you preferred language doesn't support them ;-)

Imagine you need to manipulate a String in certain ways, such as escaping special characters. Different scenarios require different escaping algorithms :

  • To escape text so that it's suitable as a Java (or Feather) string constant, we enclose it in double quotes, replace new-line characters withBACKSLASH netc
  • To escape a unix-style command line argument, we replace SPACE with BACKSLASH SPACE etc.

WE can face a situation where the place we need to escape the text does not know which escaping algorithm is required. So we can pass the escaping function as a parameter :

fun doSomething( text : List<String>, escape: (String)->String ) {
    for (line in text) {
        escape( line )
        ...
    }
}

If we were using Java, we could do a similar thing by defining an interface, and passing an implementing class :

interface Escaper {
    fun escape( toEscape : String ) : String
}

fun doSomething( text : List<String>, escaper: Escaper ) {
    for (line in text) {
        escaper.escape( line )
        ...
    }
}

However, this is less convenient. You may well include a Java library which helps escape HTML, and another to escape operating system commands, and a third to escape java string constants. All of them will be defined as static methods, and even if there were non-static methods, they wouldn't adhere to a common interface. So your only choice it to write wrapper classes or each of them. Java is criticised for its bloat for a reason! The interface version above is longer, even BEFORE adding the extra wrapper cruft!

There's also a case to be made to stop treating everything as an object, which the industry in general disagrees with, but IMHO, the argument has merits : Object-Oriented Programming is Bad by Brian Will

Functions are cleaner and simpler, and certainly in this case give classes give no benefits!

Why not use Lambdas instead?

Each strategy has its place. Lambdas are great for on-off uses, whereas functions are better when the task is of general use (such as escaping strings, in the example above).

The second reason is simple; lambdas are really tricky to implement (for me the compiler writer)! I didn't realise how weird they were before looking at the java bytecode for lambdas! I'm pretty sure that the vast majority of people don't know how they work (in Java and Kotlin), nor what their limitations.

Functions are simple to understand IMHO, and simplicity is a virtue.

Everything should be made as simple as possible, but no simpler.