Overcoming the limitations of nominal typing

Strictness with nominal typing is not always desirable. Consider the following three nominally unrelated types that might be part of a third-party Java library whose code you do not control:

// Three difference classes that have X and Y properties
// but they have no shared inheritance nor shared interfaces that define X and Y

class UIObject {
  public var X : double
  public var Y : double
  public var H : double
  public var W : double
}
class Rectangle {
  private var _x : double as X
  private var _y : double as Y
  private var _w : double as Width
  private var _h : double as Height
}
class Point {
  private var _x : double as X
  private var _y : double as Y
}

Suppose you want to write code that just gets the X and Y properties and you want your code to work with instances of any of the three classes.

Although each of these classes defines methods for obtaining the X and Y properties, these properties do not appear as part of a common interface or ancestor class. Remember that in this example, the classes are defined in a third-party library, so you cannot control the class declarations. Their only base class in common is Object, but Object does not have the properties X or Y. To write code that reads the X and Y properties for these three different classes, you must write three distinct yet nearly identical versions of the code.

Situations like this are common. Typically programmers resolve the problem by duplicating potentially large amounts of code for each related use cases, which makes it more difficult to maintain the code.

Structural typing helps these types of situations. Use structural typing in Gosu to unite the behavior of the three classes based on the presence of being able to read the X and Y properties.

Just like you would declare an interface, define the method signatures. However, use the structure keyword:

package docs.example

structure Coordinate {
  property get X() : double // get the "X" public property or field
  property get Y() : double // get the "Y" public property or field
}

You can now create code that uses the new structural type docs.example.Coordinate.

A variable declared as the structural type can contain any object with compatible qualities defined by the structural type. For example, a variable defined to the structural type Coordinate can be assigned with a Rectangle instance. This works because Rectangle has the X and Y properties of type double:

var coord : Coordinate
coord = new Rectangle()
print("X value is " + coord.X)

To illustrate the flexibility of structural typing, suppose you want to write a function that gets the X and Y properties and prints them and performs arithmetic with the values. Using structural typing, implementing this behavior once works with any class instance that satisfies the requirements of the structural type. For example, the following function takes the structural type as a parameter:

public function printCoordinate(myStruct : Coordinate) {
  print("X value is " + myStruct.X)
  print("Y value is " + myStruct.Y)
  print("Sum of X and Y value is " + (myStruct.X + myStruct.Y) )
}

In many ways this coding style is similar to using an interface defined by the interface keyword. You can call this function and pass any object that satisfies the requirements of the structural type. However, unlike interfaces, this code works independent of inheritance or interface declarations for the classes.

Use the following example to see how this function can be used by multiple concrete classes that are unrelated. Paste the following into the Gosu Scratchpad:

// Three different classes that have X and Y properties
// but have no shared inheritance nor shared interfaces that define X and Y

class Rectangle {
  private var _x : double as X
  private var _y : double as Y
  private var _w : double as Width
  private var _h : double as Height
}

class Point {
  private var _x : double as X
  private var _y : double as Y
}

class UIObject {
  public var X : double
  public var Y : double
  public var H : double
  public var W : double
}

// DEFINE A STRUCTURAL TYPE
structure Coordinate {
  property get X() : double
  property get Y() : double
}

// Define a function that works with any class that is compatible with the structural type
public function printCoordinate(myStruct : Coordinate) {
  print("X value is " + myStruct.X)
  print("Y value is " + myStruct.Y)
  print("Sum of X and Y value is " + (myStruct.X + myStruct.Y) )
}

// Use that function with three different classes that all contain X and Y public fields
// or properties, but are unrelated

var p = new Point()
p.X = 2
p.Y = 3
printCoordinate(p)

var r = new Rectangle()
r.X = 4
r.Y = 5
printCoordinate(r)

var ui = new UIObject()
ui.X = 6
ui.Y = 7
printCoordinate(ui)

Running this code prints the following lines:

X value is 2.0
Y value is 3.0
Sum of X and Y value is 5.0
X value is 4.0
Y value is 5.0
Sum of X and Y value is 9.0
X value is 6.0
Y value is 7.0
Sum of X and Y value is 13.0

These examples demonstrate that structural types are statically weaker than interfaces regarding the amount of enforced type information, but their flexibility supports situations where interfaces are ineffective or impossible. Structural types extend static typing to include a broader set of real-world situations but still support concise code that catches common coding problems at compile time.