Description
The ATD language currently doesn't have a dedicated syntax for expressing dependencies on external type definitions and implementations. The current mechanisms use the special pseudo-type abstract
or use the special wrap
constructor. This is all clunky and hard to remember. As a result, users do their best to fit all the definitions in a single .atd
file. It's usually fine but in some cases, it's limiting.
Issues being addressed
- We want to split ATD files into multiple pieces that depend on one another and fit well within the structure of the application. For example, basic types such as
date
can be defined inA.atd
while two other APIsB.atd
andC.atd
use types defined inA.atd
but are otherwise independent of one another. - External implementations that are not derived from ATD files should be easier to use and reference. Such implementations are typically opaque data structures but they offer a way to view them as JSON. It could be that they support only JSON export but not import. For example, JSON can be used to dump metadata about an image loaded in memory (see example in the comments).
Proposal
- Add a top-level
import
construct that declares which ATD-compatible units the current ATD file depends on, e.g.import basic_types
. - External types are referenced using a dot notation e.g.
basic_types.date
. - Separate compilation: External types may remain opaque i.e. the code generators
atdgen
,atdj
,atdpy
, etc. should not be forced to access external ATD files when generating code for one given ATD file. The existence of an external type or its arity (number of type parameters of a polymorphic type) may not be checked because of this.
Syntax
We introduce a new top-level import
construct, which should (must?) occur at the beginning of the ATD file as it will affect all type definitions (since type definitions are reordered based on their mutual dependencies anyway).
import REAL_NAME [as SHORT_NAME]
e.g.
import basic_types as base
import time_and_date
REAL_NAME should be the name of the original ATD file without the .atd
extension if such file exists.
Referencing the types they provide must be unambiguous without having access to the external definitions. For that, the type names use the dot notation:
type msg = {
id: string;
date: time_and_date.date;
signature: base.signature;
}
Note that this import semantics is similar to Python's. We don't really need to support the form from MODULE import MEMBER
because it can be achieved using type aliases. For shorter type names, we can do this:
import basic_types as base
import time_and_date
type date = time_and_date.date
type msg = {
id: string;
date: date;
signature: base.signature;
}
Mapping to target languages
Each target programming language has a slightly different way of managing program units. It's up to the translator (atdgen
, atdpy
, ...) to do the right thing and support language-specific ATD annotations. For the OCaml and Python targets (atdgen, atdpy), useful annotations could look like this:
import JSON
<ocaml module="Yojson.Safe">
<python module="atdpy_json">
type raw_json = JSON.t
type foo = {
name: string;
bar: raw_json;
}
or more directly:
import raw_json
<ocaml module="Yojson.Safe">
<python module="atdpy_json">
type foo = {
name: string;
bar: raw_json.t;
}
For commonly used external modules such as a JSON
module, a code generator can place them in scope automatically. In OCaml for example, atdgen could emit the code open Atdgen_runtime
which would make a JSON
module directly available. For Python where the convention is for modules to use lowercase and where a json
module already exists, we might want the name JSON
to map to atdpy_json
rather than mapping to JSON
(nonstandard) or json
(already taken). The goal is to make the use of common or standard external libraries not require ATD annotations. We want the following to just work:
import JSON
type foo = {
name: string;
bar: JSON.t;
}
Note that the t
represents the type of the main data structure provided by the module as is conventional in OCaml. It's a little nicer than JSON.json
or than having to create an alias type json = JSON.json
. It's not a requirement imposed by ATD.
So far, all the implementations of an ATD module must provide the same collection of types and functions that work on those types. We could provide a way to express "hey, Python's JSON.t isn't named t
but is named json
" but it's not clear if it's necessary since many other constraints exist for a module interface to be ATD-compatible. Whoever decides that a cross-language module such as JSON
should exist, should also specify the set of types it provides.