|10:03||• Closed ticket [57c2233d50]: Alpha 2: Module definitions plus 4 other changes artifact: 98ad5befe0 user: robin.hansen|
|10:02||Add support for modules. Fixes [57c2233d50] and [abaac5803b] check-in: e155197c9a user: robin.hansen tags: trunk|
|11:00||• Ticket [57c2233d50] Alpha 2: Module definitions status still Open with 4 other changes artifact: e33019398f user: robin.hansen|
|13:27||• New ticket [57c2233d50]. artifact: 1c26f51190 user: robin.hansen|
|Title:||Alpha 2: Module definitions|
robin.hansen added on 2021-01-09 13:27:35:
The following document outlines the language proposal process: Language Proposals
To enable any practical program written in Play, it's vital that we support splitting code up in different files and still treat such code as part of the same program. This proposal looks at the language syntax for defining modules. Another proposal deals with resolving modules, that is locating a module on disk.
Module definitons are _optional_. By default the canonical module name is the same as its disk location. For instance, if there is a package foo, and a file is located at
All modules, whether in the same package or in a third-party package, can be referenced using its canonical name. As such, import statements aren't necessary. As an example, if your package depends on a hypothetical
That being said, it's quite likely that developers would want to write a module definition anyway to change how references are made or to write documentation for a given module. In those cases, the .play file would have to start with a module definiton, and it would look something like this:
defmodule: doc: """ A module containing html node constructors """ alias: impl html/internal/node exposing: html/interal/attributes :
There are several things to note with this example:
Named, unresolvable modules
By default, all definitions within a module are exposed. That is, they are accessible to outside modules. To make a definiton not accessible, you can add it to an unresolvable module:
defmodule: doc: "This module's definitons are accessible to other modules" :
Since modules are expected to share its name with its on-disk location, a module-within-a-module cannot be resolved. As such, all definitions in such modules are inaccessible.
There are a couple of benefits with this approach:
You can have as many unresolvable modules as you want within a single file. The only requirement is that the top module definiton must match the file's path, or be unnamed.
To reference a definition in the parent module, you start a name with the / character. So if the
The problem with placing things in unresolvable modules is that you break locality. Sometimes it can increase readability to have several related functions in close proximity, but only have one of them be accessible outside of the current module. For that reason I'm willing to entertain adding a
A program's entry point (meaning the function called when a program starts up) doesn't require any special syntax. Any function can be used as an entry point as long as it matches a specific type signature suited for a given platform (will be different across repl, browser, terminal, etc.). The function that should serve as the entry point for a program needs to be passed as an argument to the compiler.
robin.hansen added on 2021-01-16 11:00:06:
Based on feedback, the proposal will have the following changes:
Limited expose-by-default behaviour
Several have noted that to expose all functions in a module, and after some time I've come to agree with that view point. It is quite likely that helper functions will be more common in Play than other languages because of the lack of local variables, and such functions have no use to be part of an API.
At the same time, I still feel that being able to just create a file with minimum module boilerplate enabled more rapid prototyping, which I feel is important in the early stages of a project.
The proposal will therefore change so that modules can now define its exposed functions. The keyword for exposing functions will be "exposing:" and it will contain a whitespace seperated list of exposed functions.
The proposal currently suggests that "exposing:" is to be used to make a function available from a different module without requiring a module or alias prefix. This use will now be enabled using the "import:" keyword.
If "exposing:" is not present in a module definition, all functions are exposed.
Unresolvable modules will still be possible, but their use is now limited to prototyping a new module layout, instead of playing any role in exposing functions. A top-level module definition is required to specify unresolvable modules.
Different resolutions for first- and third-party modules
The developer will now be required to specify whether a local or a third-party module is being referenced. A module from a third-party package is now referenced with a leading slash, while local modules are referenced without a leading slash.
This means that
This has two benefits:
Function-local imports and aliases
Each function definition is now allowed to specify its own imports: and alias: keywords. The motivation for this stems from my own experience writing Elm code.
When writing a function/constant which essentially is a Html or Css definition, it makes sense to expose all functions of the relevant packages as the context of those functions makes it pretty clear what "width" or "class" mean. However, because both those packages expose functions with common names (like "node", "height", "size" etc.) it's rarely a good idea to actually expose all functions within those modules.
With support for local imports and aliases, this would be simpler:
# Functions of /html and /html/attributes now only imported within this function definition
In comparison to using module-wide aliases:
defmodule: alias: attributes /html/attributes :
Entry point override
As mentioned in the proposal, an entry point can be provided to the compiler. It should also be possible to define an entry point in the play.json file for the project. In the case where one entry point is defined both in play.json and as an argument to the compiler, what is passed to the compiler takes precedence.
robin.hansen added on 2021-06-02 10:03:38:
Modules within modules has been postponed for now.