ahrefs/atd

Provide a way to define simple defaults regardless of target language

mjambon opened this issue · 0 comments

Instead of this:

type foo = {
  ~answer
    <python default="42">
    <ocaml default="42">: int;
}

We want this:

type foo = {
  ~answer <default="42">: int;
}

or maybe even use a dedicated syntax for it such as

type foo = {
  answer: int = 42;
}

Why wasn't it done before?

There was the need to use default values that are defined in some code outside of ATD. For example, in OCaml, we would pull the default value from another module using My_defaults.default_answer. This is an OCaml expression and we would need the equivalent for Python.

It would have been possible to create a language for constructing values within ATD. It would also have been possible to create a module system for ATD. Both of these things were considered extra complexity and extra work at the time (12 years ago) and it hasn't been revisited since.

What would it take?

  1. This feature affects all the target languages, so we'd have to update all the code generators to support the new feature, although we can also document it as unsupported for this or that language.
  2. We would keep the language-specific defaults and use the generic defaults as a fallback.
  3. We need to define a syntax for expression ATD values. This should be straightforward and it should probably imitate the OCaml syntax. Values might be limited to simple expressions that don't require special type definitions in ordinary languages. This might exclude records, tuples, and sum types other than pure enums. All expressions would be literals (e.g. 42 but not 40 + 2).
  4. We could add support for named constants e.g. let default_answer = 42. This would be useful for sharing defaults across various fields, types, or modules.

Who would benefit from this?

Any multi-language use of ATD, as it was originally intended, as opposed to using it for only one language (typically OCaml) or maybe two.

Syntax proposal

Default field values:

type foo = {
  ~answer <blah foo="bar">: int <blah> = 42;
}

The ~ could be optional since the = syntax implies a default, giving us

type foo = {
  answer <blah foo="bar">: int <blah> = 42;
}

Named constants:

let default_answer = 42

type fruit = Apple | Orange

let default_fruit = Orange

(* no support for records, tuples, or variants with arguments *)

type foo = {
  ~answer: int = default_answer;
  ~chosen_fruit: fruit = default_fruit;
}

Implementation issues

The particular representation of an ATD type is up to the target language and may be affected by ATD annotations. Therefore, a value specified as 42 in ATD would be inferred as an ATD int but could translate to a JavaScript number (a 64-bit float), a Python int of unlimited precision, or a fixed-width twos-complement int (32/64 bits in many languages, 31/63 bits in OCaml). The ATD parser and code generator should settle on a representation that works for all target languages:

  • for ints, use unlimited precision (possibly a string since we don't need to do arithmetics on it, saving the dependency on a library)
  • for floats, use a string (to accommodate floating-point numbers other than 64-bits)
  • the handling of other types is more obvious

We should assume a module system based on the current proposal, in which an ATD module is translated without having access to other ATD modules. This means that a constant declaration let default_answer = 42 needs to be either restricted to local use (not visible from other modules :-( ) or expressed in the correct format (e.g. an OCaml int64 literal if an annotation calls for it: let answer = 42L). This implies that format annotations such as <ocaml repr="int64"> can only be applied on the let definition:

let answer : int <ocaml repr="int64"> = 42
...
type foo = {
  x: int <ocaml repr="int64"> = answer;  (* redundant format annotation; not sure how to avoid it *)
}

This would not work:

let answer = 42
...
type foo = {
  x: answer <ocaml repr="int64">;  (* illegal/ignored format annotation *)
}