Skip to content

TM005 More structured module system, to facilitate data, renaming, bringing identifiers into local scope

Daniel Patterson edited this page May 17, 2013 · 2 revisions

Motivation:

Pyret's current module system does not allow data types to be declared in one module and used in annotations in another. This is because an annotation of Foo results in a call to a function Foo?, which is generated by desugaring of 'data' (and intentionally is not shadowable by user code), but there is no way of importing that identifier, as you can neither write that function in the provide block of the exporting module, nor define that identifier in the importing module. This proposal aims to increase the structure of the provide block, to facilitate exporting 'data', and also making it easier to rename-out and rename-in. The syntax is intended to be lightweight, and the exporting and importing should be similar.

Example:

foo.arr
---------
provide:
  data(Foo)
  foo
  bar as g
end

data Foo:
  | foo(x, y, z)
end

fun foo(): 1 end

fun bar(): 2 end


bar.arr
---------
import "foo.arr" as foo:
  foo as foobar
end

fun f(a :: Foo):
  a.x
end

foo.foo()
foo.g()

foobar() # is the same as foo.foo()

Semantics:

'provide' is now a list of provide expressions, which are: data(identifier) | identifierA | identifierA as identifierB

The first causes all the relevant functions / type information to be exported (all constructors, all predicates), identifierA is the same as 'identifierA as identifierA', and the as clause renames identifierA to identifierB when exporting.

When a module is imported, all of the data exports are brought into scope, so you can use the constructors, and write the types in annotations. The motivation is that if a module is exporting a data definition, it is probably very important to that module, and thus you will want to use it, and we should make it as easy as possible to do so (so, for example, if you import option, you can just write some(v) and none, instead of option.some(v) and option.none). If that turns out not to be the case, we should change this, or perhaps make there be a way to disable this behavior, perhaps with a 'hiding' clause, like:

import "foo.arr" as foo:
  foo as foobar
hiding
  data(Foo)
end

Though this is a little confusing, because it doesn't really make sense to hide anything else. Perhaps hiding(Foo) is an import expression? ie:

import "foo.arr" as foo:
  foo as foobar
  hiding(Foo)
end

Inside the import clause, there are import expressions, which are: identifierA as identifierB | identifierA

Where again identifierA alone is synonymous to identifierA as identifierA, and the 'as' statement causes the identifier with name identifierA to be bound in the current scope as identifierB.

Finally, the current behavior will be preserved - all of the exports of the module (except for those related to 'data' exports) will be accessible in the object that the module was imported as. Renaming within the import clause will not rename within that module. This means that if you see mod.foo, you know definitively what it means (provided you know what mod is) - the identifier foo exported by mod.