Home

Awesome

Traitor

A trait library made from bordem.

Docs

What does it do?!

Traitor allows one to use to use tuples to describe interfaces that can ensure types implement procedures for runtime dispatch.

The trait tuple must be an distinct tuple. All procedures must have Atom appearing only once as the first argument. All trait procedures should be annotated with their appropriate calling convention, as Nim defaults to {.closure.} for types you must annotate it {.nimcall.}.

The following is an example:

import traitor
type
  Clickable = distinct tuple[ # Always use a distinct tuple interface to make it clean and cause `implTrait` requires it
      over: proc(a: Atom, x, y: int): bool {.nimcall.}, # Notice we use `Atom` as the first parameter and it's always the only `Atom`
      onClick: proc(a: Atom): string {.nimcall.}]
  UnimplementedTrait = distinct tuple[
    overloaded: ( # We can add overloads by using a tuple of procs
      proc(a: var Atom) {.nimcall.},
      proc(a: Atom, b: int) {.nimcall.})
    ]

  Button = object
    x, y, w, h: int

  Radio = object
    x, y, r: int

implTrait Clickable

proc over(btn: Button, x, y: int): bool =
  btn.x < x and (btn.x + btn.w) > x and btn.y < y and (btn.y + btn.h) > y

proc onClick(btn: Button): string = "Clicked a button"

proc over(radio: Radio, x, y: int): bool =
  radio.r >= (abs(radio.x - x) + abs(radio.y - y))

proc onClick(radio: Radio): string = "Clicked a radio"

emitConverter Button, Clickable # Emit a `converter` for `Button` -> `Traitor[Clickable]`

type ClickObj[T] = TypedTraitor[T, Clickable]

var
  elements = [
    Traitor[Clickable] Button(w: 10, h: 20), # We can convert directly if we use `emitConvert`
    Button(x: 30, y: 30, w: 10, h: 10),
    Radio(x: 30, y: 30, r: 10).toTrait(Clickable) # Otherwise we need to convert with `toTrait(trait)`
  ]

assert elements[0].over(3, 3) # We can call procedures as if they're normal
assert elements[1].over(33, 33)
assert elements[2].over(30, 30)

for i, x in elements:
  if x of  TypedTraitor[Button, Clickable]: # We can use `of` to check if it's the given type
    assert i in [0, 1]
  elif x of ClickObj[Radio]:
    assert i == 2

assert elements[2].getData(Radio) == Radio(x: 30, y: 30, r: 10) # We can use `getData` to extract data
elements[2].getData(Radio).x = 0 # It emits a `var T` so it can be mutated
assert elements[2].getData(Radio).x == 0

let elem = ClickObj[Button](elements[0])
elem.setProc onClick, proc(arg: Traitor[Clickable]): string = "Hmmmm"
for i, x in elements:
  let msg = x.onClick()
  case i
  of 0:
    assert msg == "Hmmmm"
  of 1:
    assert msg == "Clicked a button"
  of 2:
    assert msg == "Clicked a radio"
  else:
    assert false

for i, elem in elements:
  elem.unpackIt:
    if i in [0, 1]:
      assert it is TypedTraitor[Button, Clickable]
    else:
      assert it is TypedTraitor[Radio, Clickable]

Clickable.repackIt(0):
  assert It is TypedTraitor[Button, Clickable]

Clickable.repackIt(1):
  assert It is TypedTraitor[Radio, Clickable]

Additional information

Defining -d:traitorNiceNames can be used to make the generate procedures have nicer names for debugging.