Fan

 

Methods

Overview

A method is a slot which defines a function within a class or mixin:

class Boo
{
  static Int add(Int a, Int b) { return a + b }
  Int incr() { return count++ }
  Int count := 0
}

In the example above add and incr are method slots on the class Boo. The incr method is an instance method which means it is always invoked on an instance of Boo. The add method is static and is not invoked on an instance:

b := Boo.make
x := b.incr()
y := Boo.add(3, 4)

Method invocation is performed using the . dot operator on a target. The target for instance methods is an instance of the type; for static methods the target is the type name.

Methods in your own type (or types you inherit) are automatically scoped such that the target type or instance is implied. For example:

class Foo : Boo
{
  Int more() { return incr() + add(3, 4) }
}

If the method does not take any parameters, then we can leave off the () empty parenthesis. By convention the empty parenthesis are always omitted:

b.incr  // same as b.incr()

You can also the ?. operator to safely handle a null target. See safe invokes.

This

Instance methods always have an implied first parameter which is the instance itself identified via the keyword this. The definitions of a and b are identical in the following example:

class Foo : Boo
{
  Int a() { return incr }
  Int b() { return this.incr }
}

Constructors

Constructors are special methods used to create new instances of a class. In Fan, constructors are named methods. The difference is that they use the new keyword in their definition instead of a return type (the return type is implied to be an instance of the type):

class MissingPerson
{
  new make(Str name) { this.name = name }
  Str name
}

By convention, the primary constructor should be called make and other constructors should be prefixed with make. Like other slots, constructors must be uniquely named within their type. To create an instance, you call the constructor like a static method:

jack := MissingPerson.make("Jack Shephard")

You can also use the shorthand syntax for calling a constructor called "make":

sayid := MissingPerson("Sayid Jarrah")

From a client perspective, constructors look just like named factory methods (in fact switching between a static method and constructor maintains source level compatibility, but not binary compatibility). Constructors have the unusual property of acting like a static method on the outside and an instance method on the inside. On the outside you call a constructor like a static method and get back an instance of the type. On the inside of a constructor, the instance has already been allocated and is available using the this keyword.

If you do not declare a constructor on your class, then the compiler will automatically generate a public no arg constructor called make.

Only classes can have constructors. It is a compile time error to declare a constructor on a mixin.

Construction Calls

Fan supports a special syntax called construction calls with the syntax Type(args). These calls are resolved as follows:

  1. bind to Type.fromStr if there is exactly one Str argument and Type.fromStr exists (see simple literals)
  2. otherwise bind to Type.make

The construction operator works on static methods or constructors. So you can use it when make is a constructor or when make is a factory method. The method of construction call Foo(...) must return Foo.

Convention is to always prefer a construction call to using make explicitly:

ArgErr.make   // non-preferred
ArgErr()      // preferred

ArgErr.make("bad arg")   // non-preferred
ArgErr("bad arg")        // preferred

Constructor Chaining

When creating subclasses, you must call one of your parent class constructors or another of your own constructors using a syntax called constructor chaining. The syntax to call a parent constructor is based on C++ and C# using the : after the formal parameters, but before the method body:

class Foo
{
  new make() {}
  new makeName(Str name) {}
}

class Bar : Foo
{
  new make() : super() {}
  new makeFullName(Str? first, Str last) : super.makeName(last) {}
  new makeLastName(Str last) : this.makeFullName(null, last) {}
}

All constructor chains start with the this or super keyword. Use this to chain to one of your own constructors or super to call a parent constructor. Then the constructor to call is specified as a normal method call with the name and argument list. As a shortcut, you can omit the name if the parent constructor being called has the same name.

In the example above, Bar.make illustrates calling Foo.make- omitting the name implies calling a parent of the same name - make in this case. Bar.makeFullName illustrates calling a super class constructor by name. Bar.makeLastName shows how to call a peer constructor on your own class - this is useful for ensuring all your initialization code is centralized in one constructor.

Static Constructors

Static constructors are methods executed to initialize the class itself. They are typically used to initialize static fields. Static constructors use a Java like syntax:

class Foo
{
  static { echo("initializing Foo...") }
}

Assignment to static fields is done in an auto-generated static initializer. It is permissible to have multiple static initializers, in which case they are run in the order of declaration:

class Foo
{
  static const Int a := 10
  static { echo("1st a=$a b=$b") }
  static const Int b := 20
  static { echo("2nd a=$a b=$b") }
  static { a = 30 }
  static { echo("3rd a=$a b=$b") }
}

// outputs
1st a=10 b=null
2nd a=10 b=20
3rd a=30 b=20

Default Parameters

You can specify a default argument for parameters. Defaults can be applied to the last zero or more parameters (right to left). For example:

static Int add(Int a, Int b, Int c := 0, Int d := 0)
{
  return a + b + c + d
}

In this example the last two parameters c and d default to zero. This allows you to call the add method with 2, 3, or 4 arguments:

add(3, 4, 5, 6)
add(3, 4, 5)     // same as add(3, 4, 5, 0)
add(3, 4)        // same as add(3, 4, 0, 0)

Shortcuts

Fan supports operator overloading using shortcut methods. Basically any method that uses one of the predefined shortcut method signatures can be invoked via its associated operator. For example if you wish to create a class that supports Str indexing using the [] operator you would just write a get and set method:

class FlightManifest
{
  Passenger get(Str name) { ... }
  Void set(Str name, Passenger p) { ...}
}

kate := flight815["Kate Austen"]  // kate := flight815.get("Kate Austen")
flight815["Hugo Reyes"] = hugo    // flight815.set("Hugo Reyes", hugo)

See Shortcut Operators for the full mapping of operators to method signatures.

Virtual Methods

Virtual methods are designed to be overridden by a subclass to enable polymorphism. Methods must be marked using the virtual keyword before they can be overridden by subclasses. Subclasses must declare they are overriding a method using the override keyword:

class Animal
{
  virtual Void talk() { echo("generic") }
}

class Cat : Animal
{
  override Void talk() { echo("meow") }
}

Animal().talk   // prints generic
Cat().talk      // prints meow

Abstract Methods

Abstract methods are virtual methods without an implementation. They are declared using the abstract keyword. Abstract methods are implied to be virtual - it is an error to use both the abstract and virtual keyword. Abstract methods must not provide a method body. If declared within a class, then the containing class must also be abstract.

Once Methods

The once keyword can be used to declare once methods. A once method only computes its result the first time it is called and then returns a cached value on subsequent calls. Once methods are a great technique for lazily creating state without a lot of boiler plate code:

// hard way
Str fullName
{
  get
  {
    if (@fullName == null) @fullName = "$firstName  $lastName"
    return @fullName
  }
}

// easy way
once Str fullName() { return "$firstName  $lastName" }

Restrictions for once methods:

  • Must not be declared within a const class
  • Must not be declared within a mixin
  • Must not be a constructor
  • Must not be static
  • Must not be abstract
  • Must return non-Void
  • Must have no parameters

If a once method throws an exception, then there is no cached value - subsequent calls will re-execute the method until it returns a value.

Covariance

Fan supports covariance - which allows an overridden method to narrow the return type of the inherited method:

abstract class Animal
{
  abstract Animal mommy()
  abstract Animal daddy()
}

class Cat : Animal
{
  override Cat mommy() {...}
  override Cat daddy() {...}
}

This Returns

A method declared to return This is a special case of covariance which always returns the type being used. This technique is typically used by methods which return this to enable method chaining. Consider this example:

class Connection
{
  Connection open() { return this }
}

class MyConnection : Connection
{
  MyConnection talk() { return this }
}

The APIs are written to allow method chaining, so we'd like to be able to write something like this:

MyConnection.make.open.talk

If you actually tried to compile that code you'd get an error like "Unknown slot Connection.talk". We could write code without method chaining, or we could even use the "->" operator. But this technique is so commonly used, that Fan allows you to declare the return type as This:

class Connection
{
  This open() { return this }
}

class MyConnection : Connection
{
  This talk() { return this }
}

The This type is a special marker type like Void. It indicates that a method is guaranteed to always return an instance of the target type. In our example above, the expression x.open will always evaluate to an instance of x.type.

Use of This is restricted to the return type of non-static methods. You can't use it for static methods, parameter types, local variable types, or for fields. Overrides of a methods which return This must also return This.

Dynamic Invoke

As any dynamic language proponent can tell you - sometimes static typing can be a real pain. So Fan supports a hybrid static/dynamic design by providing two call operators. The . dot operator accesses a slot using static typing - if the slot cannot be resolved at compile time, then it results in a compile time error.

The -> dynamic invoke operator lets you perform calls with no compile time type checking. What dynamic invoke actually does it generate a call to the sys::Obj.trap method. By default the trap method uses reflection to lookup and call the method. If the name maps to a field, then trap will get or set the field depending on the number of arguments:

a->x        a.trap("x", [,])
a->x = b    a.trap("x", [b])
a->x(b)     a.trap("x", [b])
a->x(b, c)  a.trap("x", [b, c])

In the simplest case, the -> operator is syntax sugar to by-pass static type checking and use reflection. But the ability to override the trap method is a powerful technique in the Fan toolkit for building dynamic solutions.

You can also the ?-> operator to safely handle a null target. See safe invokes.

Native Methods

Native methods are implemented in an alternate language which is "native" for each target platform. Native methods are typically written in Java for the Java VM and C# for the .NET CLR. Native methods use the native keyword and must not have a method body (like abstract methods). The infrastructure for supporting native methods is discussed in the Natives chapter.