Fan

 

DynamicTypes

Overview

Most types are defined as a class or mixin in Fan source code. These types are available for reflection at runtime using the sys::Type API. But Fan also let's us create new types at runtime. A type created at runtime is called a dynamic type.

Dynamic types enable us to plug new data models into the Fan type system without having to map things into Fan source code. The reflection APIs sys::Type, sys::Field, and sys::Method can all be subclassed and customized. For example the sql APIs let us map relational database tables into the standard type system for ad hoc queries.

Dynamic types are an important feature for reducing the coupling between subsystems. By modeling everything in the native Fan type system, different subsystems can exchange data models using nothing more than the APIs found in sys. For example let's say I want to create a pod responsible for exporting a table to CSV - our API should export any List using reflection to find the Fields. Now any subsystem which generates lists of objects with reflective fields can use that API. Contrast this with a platform like Java where a "table" might be modeled as an array, java.util.List, java.sql.ResultSet, or maybe even a Swing TableModel. In Fan, a "table" should be a List with columns defined by Fields. To be a good Fan citizen, try to map your meta-data models into Types and Slots.

Dynamic Types

All dynamic types begin life with the sys::Type.makeDynamic constructor. The first argument to this constructor is a list of Types for the dynamic type to extend. This list of types has the same semantics as the list of super types in your source code:

class D : A, B, C {}

x := D#
y := Type.makeDynamic([A#, B#, C#])

In the code above both x and y are Types which implement A, B, and C. All the reflection APIs work the same between x and y such as fits, inheritance, and slots.

However, the dynamic type referenced by y is unnamed. It is not part of a pod, nor does it have a global qualified name. So methods like pod and qname behave a bit different.

The list of types passed to makeDynamic has some restrictions. For example you can't extend any final, const, or abstract classes.

NOTE: currently Fan doesn't support makeDynamic with anything but a single class. Sometime soon we'll let you pass in mixins and generate a class on the fly (TODO).

Dynamic Instances

Objects created from a dynamic type are called dynamic instances. Dynamic instances are created by calling sys::Type.make on a dynamic type. Instances created from a dynamic type are bound to that type via sys::Obj.type. But the objects are also subclasses of all the types passed to makeDynamic, so you can cast them and use them like normal objects too.

Dynamic Slots

You can add and remove slots on a dynamic type at runtime. Slots added to a dynamic type are called dynamic slots (bet you didn't see that one coming). You add and remove slots via sys::Type.add and sys::Type.remove.

Dynamic Field

Dynamic fields are implemented by subclassing sys::Field and overriding the get and set method. Access to the field is thru normal reflection or the dynamic invoke operator. Here is a simple example that stores dynamic fields in DynoObj.vals:

class DynoObj
{
  static Void main()
  {
    // create our dynamic type
    t := Type.makeDynamic([DynoObj#])
    t.add(DynoField("name", Str#))
    t.add(DynoField("drinksPerDay", Int#))

    // create an instance
    DynoObj obj := t.make
    obj->name = "Kara Thrace"
    obj->drinksPerDay = 17
    echo("${obj->name} ${obj->drinksPerDay}")
  }

  Str:Obj vals := Str:Str[:]
}

const class DynoField : Field
{
  new make(Str name, Type of) : super(name, of) {}
  override Obj get(Obj o) { return ((DynoObj)o).vals[name] }
  override Void set(Obj o, Obj v) { ((DynoObj)o).vals[name] = v}
}

A real world example of dynamic fields is sql::Col.

Dynamic Method

Dynamic methods are implemented via Functions. You can create a method via the sys::Method.make constructor directly, or you can create your own method subclasses. Here is a simple example which adds a dynamic method which adds two fields together:

class Dyno
{
  static Void main()
  {
    // create dynamic type, and instance of that type
    t := Type.makeDynamic([Dyno#])
    Dyno obj := t.make

    // add a dynamic method to t
    func := |Dyno o->Int| { return o.a + o.b }
    t.add(Method("add", func))

    // call dynamic method - prints "8"
    echo(obj->add)
  }

  Int a := 3
  Int b := 5
}