Skip to content

Defining type operations ​

While type-macros operate on arguments in unparsed form, type-operations allow operating on arguments after they have already been converted to types. This makes operations much more powerful, but also more difficult to define.

emacs-lisp
(et-defop NAME (ARGS...)
  [:to-string TO-STRING-CODE] ; always optional
  [:parse PARSE-CODE] ; only required for very advanced defops
  EVAL-CODE)

This will auto-generate functions for parsing, printing, and evaluating the operation.
The :parse function takes the raw spec arguments, and returns the parsed versions of the arguments (parsing the spec into the et:repr).
The :to-string function takes the parsed arguments, and returns a printed string representation.
The evaluation function takes the parsed and evaluated arguments, and returns a modified type object.

In the simplest case, where all of the arguments to the operation are types, :parse can be ignored. et-defop auto-generates a parse function which parses each spec to a et:repr. Additionally, the parsed arguments (in et:repr form) will be converted to et:type objects before being passed to EVAL-CODE. Then, EVAL-CODE can simply operate on the final types.

emacs-lisp
(et-defop minus (a b)
  (et-subtract a b)) ; both a and b are type objects

Sometimes, not all arguments to an operation are types. For example, consider the infer operation. infer matches TYPE against PATTERN. If it matches, it evaluates IF, and if not, it evaluates ELSE.
Additionally, as stated before, type-arguments are automatically converted to types before being passed to EVAL-CODE. However, we do not want to evaluate YES or NO until we have checked whether the infer matched. So, these can be annotated with :repr to state that these should be passed as et:repr objects (not be pre-evaluated).

emacs-lisp
;; (infer TYPE [GENERICS...] PATTERN IF ELSE)
(et (infer List<Integer> [T] List<T> T : Never))
;; -> Integer
(et (infer Boolean [T] List<T> T : Never))
;; -> Never

The definition of infer looks like this:

emacs-lisp
(et-defop infer (type [genvec :generics] [matcher :matcher] [yes :repr] [no :repr])
  ...)

Each argument (other than type) is paired with a keyword. The keyword specifies how the operation argument is parsed. Here, :generics refers to a vector of generics, :matcher refers to a pattern which types can be matched against, and :repr refers to an unevaluated type-expression.
For more information on defining advanced defops, reading through the examples defined in ;;;; Op definitions in et.el will be far more useful than this meager docs page.