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 }