Note! Fabric is a work-in-progress project under active development.
Documents
- Abstract with extended background and motivation.
Overview
Type system
Fabric's type system follows the framework of algebraic subtyping, as set out by Stephen Dolan in his thesis (MLsub) and later refined by Lionel Parreaux (Simple-sub, MLstruct). When following this tradition, we define a lattice algebra over types, with meets and joins over any pair of types. For instance, in MLsub: $$ \texttt{select}\,p\,v\,d = \texttt{if}\, p\,v \,\texttt{then}\, v \,\texttt{else}\, d $$ $$ \forall \alpha, \beta \ldotp (\alpha \to \texttt{bool}) \to \alpha \to \beta \to (\alpha \sqcup \beta) $$ In Fabric I implement the type system following MLstruct, which removes the polarity restriction and supports a limited form of negation types. I explore the capabilities and limitations of this Boolean algebraic approach to algebraic subtyping. Subtyping, as in similar work, is implicit in Fabric.
Records
A key feature present in both statically and dynamically typed languages are aggregate record
types. Dynamic languages (Python, JavaScript, Lua) often call these objects, and check
attribute accesses at runtime. I believe we can get much of the benefits of objects without sacrificing
safety by typing them structurally. In the past, successful approaches to this have relied on the
application of row polymorphism
– an extension to Hindley-Milner type systems (e.g. OCaml's
object types, or PureScript's
records).
(* type annotations are added for clarity *)
let p: {x: int, y: int} = {x: 3, y: 4}
let cp: {x: int, y: int, c: [ Green ]} = {c: Green | p}
let hypot v = v.x*v.x + v.y*v.y
let h = hypot cp (* 25 *)
I propose to use algebraic subtyping as a basis for typing records instead, without the inclusion of row
polymorphism. To make this possible, I propose a solution to the update problem of subtyping (see Cardelli and
Mitchell).
Arrays
It is known that structural types (be it via row polymorphism or otherwise) can be useful for typing operations on dataframes. However, this approach remains unexplored for arrays, with the array programming community failing to discover a satisfactory approach to typing array programs in modern workloads (e.g. for machine learning models and scientific computing). Most widely adopted languages only give a cursory treatment of array types, only providing (some of) the following:- A single-dimensional array (vector) type
- A multi-dimensional array of arbitrary dimension
- A family of nominal k-dimensional array types
- Multiple dimensions are modelled as (Cartesian) products.
- Concatenations are modelled as (ordered) sums.
Code generation
I implemented a Fabric compiler into WebAssembly, a modern and
portable compilation target.
WebAssembly is a convenient target. WebAssembly runtimes are shipped in most web browsers, but local
runtimes and ahead-of-time compilers also exist. It aims to provide a solid abstraction over hardware. I
thus expect its performance to be a good baseline above other possible targets.
I rely on the Binaryen
toolchain for generating WebAssembly code. Fabric is a managed memory language, and so I made rich use
of
the recently adopted WasmGC extensions.
The main motive that leads me to look at code generation is that structural subtyping requires careful
compilation schemes for its core data structures (records, variants). I thus aim to answer the following
questions:
- Can we quantify the performance impacts of language features using structural subtyping?
- How does the performance of different record representations compare?
Properties
Many type systems limit the user's capabilities to define their own rules in the type system. Powerful type system features like GADTs permit encoding this, but the proof burden is often left to the user.I would like to explore a structural system of properties, describing extra information about a value and supplementing its type. Properties can be required by a function, and they can be derived from other known properties via user-defined inference rules.
(* say, BLAS routine for symmetric matrix multiply *)
val symm : matrix @ {symm} -> matrix -> matrix -> matrix
(* encode rules on higher-level properties of matrices *)
rule {upper_triangular, lower_triangular} |- {diagonal}.
rule {orthogonal} |- {invertible}.