2.7 Abstract

An abstract type is a type which is actually a different type at run-time. It is a compile-time feature which defines types "over" concrete types in order to modify or augment their behavior:

abstract AbstractInt(Int) {
  inline public function new(i:Int) {
    this = i;
  }
}

We can derive the following from this example:

  • The keyword abstract denotes that we are declaring an abstract type.
  • AbstractInt is the name of the abstract type and could be anything conforming to the rules for type identifiers.
  • The underlying type Int is enclosed in parentheses ().
  • The fields are enclosed in curly braces {},
  • which are a constructor function new accepting one argument i of type Int.
Define: Underlying Type

The underlying type of an abstract is the type which is used to represent said abstract at runtime. It is usually a concrete (i.e. non-abstract) type but could be another abstract type as well.

The syntax is reminiscent of classes and the semantics are indeed similar. In fact, everything in the "body" of an abstract (everything after the opening curly brace) is parsed as class fields. Abstracts may have method fields and non-physical property fields.

Furthermore, abstracts can be instantiated and used just like classes:

class Main {
  static public function main() {
    var a = new AbstractInt(12);
    trace(a); // 12
  }
}

As mentioned before, abstracts are a compile-time feature, so it is interesting to see what the above actually generates. A suitable target for this is JavaScript, which tends to generate concise and clean code. Compiling the above using haxe --main MyAbstract --js myabstract.js shows this JavaScript code:

var a = 12;
console.log(a);

The abstract type Abstract completely disappeared from the output and all that is left is a value of its underlying type, Int. This is because the constructor of Abstract is inlined - something we shall learn about later in the section Inline - and its inlined expression assigns a value to this. This might be surprising when thinking in terms of classes. However, it is precisely what we want to express in the context of abstracts. Any inlined member method of an abstract can assign to this and thus modify the "internal value".

One problem may be apparent - what happens if a member function is not declared inline? The code obviously must be placed somewhere! Haxe handles this by creating a private class, known as the implementation class, which contains all the abstract member functions as static functions accepting an additional first argument this of the underlying type.

Trivia: Basic Types and abstracts

Before the advent of abstract types, all basic types were implemented as extern classes or enums. While this nicely took care of some aspects such as Int being a "child class" of Float, it caused issues elsewhere. For instance, with Float being an extern class, it would unify with the empty structure {}, making it impossible to constrain a type to accept only real objects.