Modules

Modules provide a way to structure contract specifications into blocks. An example of a module is to group related constants such as VAT rate, payment grace period, etc. to make the contract easier to read. Modules are specified using the module keyword:

module Constants {
  val vat = 0.25
  val paymentGrace = 10 // days
}

To use declarations in a module, the :: syntax is used:

val a = Constants::paymentGrace
// a is 10

Modules can be nested into other modules:

type BaseShape {}
module Shape {
  type Circle : BaseShape {
    radius : Float
  }
  module Circle {
    val pi = 3.14159
    val area = \(c : Shape::Circle) -> c.radius * c.radius * Shape::Circle::pi
  }

  type Rectangle : BaseShape {
    length : Float,
    width : Float
  }
  module Rectangle {
    val area = \(r : Shape::Rectangle) -> r.length * r.width
  }

  // Compute area for any Shape
  val area = \(s : BaseShape) ->
    type x = s of {
      Circle -> Shape::Circle::area x;
      Rectangle -> Shape::Rectangle::area x;
      _ -> 0.0
    }
}

Notice how values are accessed in modules using the ModuleName::value notation.

It is also possible to define contracts in modules:

module Sale {
  type Sale: Event {}
  val inventory = ...
  contract sale = \item -> \price -> \buyer -> ...

  val income = ...
}

module Purchase {
  type Purchase: Event {}
  val stock = ...
  contract purchase = \item -> \price -> \seller -> ...

  val expenses = ...
}

Some things to consider when working with modules:

  • You must always use the full module path to refer to declarations, except when referring to other declarations inside the same module. In the example above, we wrote Circle and Rectangle to refer to the circle and rectangle types, but Shape::Rectangle::area and Shape::Circle::area were required to refer to the area functions due to them not being defined in exactly the same module.
  • Modules do not provide any isolation or restrict access: Any value/type/contract defined in a module is accessible with the full module path.