A shape describes the keys of a record, in order. It is the record’s type. All records with the same keys in the same order share exactly one shape.
a := { name: 'Joe' }
b := { name: 'Rich' }
a? # <gab\shape name:>
b? # <gab\shape name:>
(a?) == (b?) # true:
gab\shape
There are multiple ways to construct a shape value.
From a record, using the ? message:
Person := { name: '', age: 0 }?
# <gab\shape name: age:>
Directly, using Shapes.make: with a list of keys, or the shape syntax:
Person := Shapes.make(name:, age:)
# <gab\shape name: age:>
Person := \{ name: age: }
# <gab\shape name: age:>
Both produce the same shape value.
Shapes as specialization targets
Shapes are most useful as receiver types in def:, defcase:, and defmodule:. All records with a matching shape will respond to the defined message:
Person := \{ name: age: }
birthday: .def (Person, () :: do
'Happy Birthday, $!'.sprintf(self.name).println
self.put(age: self.age + 1)
end)
bob := { name: 'bob', age: 44 }
bob := bob.birthday
# Happy Birthday, bob!
bob.age # 45
Shapes in dispatch
When gab resolves a message send, the shape is checked as the super type before the record’s base type (gab\record). This means shape-specialised messages take precedence over general record behaviour:
foo: .def (\{ x: }, 'shape case')
foo: .def (Records.t, 'record case')
foo: .def 'general case'
{ x: 1 }.foo # 'shape case' (shape wins over general)
{ y: 1 }.foo # 'record case'
'something else!'.foo # 'general case'
See Messages — Dispatch Resolution Order for the full sequence.