Modules in Pliant language

Introduction

Pliant compiling engine contains a single dictionary to connect identifiers to definitions. An identifier can be the result of parsing a keyword such as 'a_word', but also any sign such as '+='. A definition can be a constant, or a function, or a meta function.
Several definitions can be associated with each keyword.
Then each definition is associated with a module.

Now, when an identifier is used in a program, the program is part of a module, and the module will provide informations of others module it want to use or ignore the symbols of.

Exporting identifiers

When some new identifiers definitions are provided in a module (functions, methods, meta functions, global variables, constants), they are local to the module.
'export' keyword can be used to make them global so that modules later including this one through 'module' instruction (see bellow) will see them:

gvar Int a_variable
function fact x -> y
  arg Int x y
  ...
export a_variable fact

Please notice that fields in a type definition are also local until they are exported:

type Pair
  field x y
export Pair '. x' '. y'

Sometime a lot symbols are to be exported, so it is more convenient to temporary change the default to 'all new definitions will be automatically exported':

public
type Pair
  field x y
private
gvar Int a_local variable

An alternative way to use 'public' is to use indentation. In this case, no 'private' is needed to restore the default behavior bellow the bloc.

public
  type Pair
     field x y

Exported symbols can be restricted to a subpart of Pliant tree:

scope "/my_corp/"

means that only modules starting with '/my_corp/' can access the symbols exported by this one through using 'module' instruction bellow.
This is very useful to make some symbols locals not to a module, but to a project.

In some complex cases, modules can include each other in a ring. The standard behavior when such a ring is detected is to genenate an error. If no error should be generated because the partially compiled module already exported the symbols the other one requires, you can use:

ring_module

Importing identifiers

'module' instruction provide two things at one. First, if the specified module was not loaded yet, it will be compiled on the fly. Then, the specified module is added to the current module list of viewed module, so that all the symbols it exports can now be used in the current module:

module "/pliant/language/unsafe.pli"

'submodule' does the same as module, but also say that any module that will later use 'module' to view identifiers exported by the current one will automatically also see identifiers in the module passed as a parameter to 'submodule'.

submodule "/pliant/graphic/image/prototype.pli"

Duplicating identifiers

'alias' is an advanced instruction that enables to pick any identifier in any module, and export (duplicate) it to another module under another name:

alias doc document from "/pliant/language/basic/safe.pli" in "/my_corp/my_app/another.pli"

In this sample 'document' identifier defined in /pliant/language/basic/safe.pli is copied as 'doc' identifier in /my_corp/my_app/another.pli module.
'from' and 'in' parameters are optional. If not provided, the current module will be used.

The plugin mechanism

A key advantage of free softwares is to enable power users to modify them according to their particular needs. The huge technical problem it raises is: what to do with these changes. Either they will be contributed back to the common project, and it will grow and become bloated as a result so that new users will loose the initial possibility because mastering the huge thing got impossible for them, or they will remain out of the main tree and it can be a serious issue for the users to upgrade their changes to each new release of the software.
The Pliant plugin notion is just a mechanism to help keep out of the main tree changes that will mostly automatically upgrade to new releases.

Let's assume that a module /a/b.pli contains the following function:

function allowed user -> a
  arg Str user ; arg CBool a
  a := true
  plugin control

Assuming that the computer name is 'demo.fullpliant.org', then you can create a module with name either:
/custom/demo.fullpliant.org/a/b.pli
/custom/fullpliant.org/a/b.pli
/custom/universal/a/b.pli
and with the following content:

custom control
  if user<>"me"
    a := false

So that, in the end, the function executed by Pliant will be:

function allowed user -> a
  arg Str user ; arg CBool a
  a := true
  if user<>"me"
    a := false

Another way to provide a plugin possibility would be:

function allowed user -> a
  arg Str user ; arg CBool a
  plugin control
    a := true

and the way to use it could be:

custom control
  a := user="me"

So that the effective function executed by Pliant would be:

function allowed user -> a
  arg Str user ; arg CBool a
  a := user="me"

As a summary, if custom is provided a single identifier parameter, then the custom code will be added, whereas if custom is provided an identifier and a program bloc, the program bloc will be replaced by the custom one.

The tree possible names for the custom module just make it easy to specify customizations that apply to a single machine, to all the machines in a domain, or to any machine that contains the custom module.

You can get the name of your computer through just entering 'Configure' menu and see the 'Identity' line in the table, or using the following code:

module "/pliant/language/context.pli"
console computer_fullname eol

As a summary, the plugin/custom mechanism is just a mechanism that makes it a little bit easier to maintain some changes out of the main Pliant tree.
The explicit identifier notion makes it a little more adaptable that Unix 'diff' that has to guess the position, but it requires inserting 'plugin' instructions at many places in the source tree (what I see as the proper way to resolve most inclusion disagreements).
Anyway, in the end, it does not solve the main issue which is to check that the changes in the core application did not break the custom part.

alias in module

Comment posted on 2008/12/09 04:25:24 by boris

In your example for alias, how can /my_corp/my_app/another.pli
compile standalone if it is missing doc definition inside it ?
Does it mean it is not designed to be used standalone ? What if doc
is defined in another.pli, does alias overwrite it ? Is this the
opposite of "export" operation ? Can this be used to replace/mock some
parts of a module (for testing purposes, for example?).

I am very interested in a way to have an ability to change any private
(non-exported) function or variable in any module externally -- is
this possible ?

ring_module

Comment posted on 2008/12/09 04:28:39 by boris

Could you provide more concrete examples ? Can module A inculde B, and B include A ?
What if I am ok to allow cyclic dependencies
for certain module combinations, but not for others ? Does it matter where in the
module the keyword ring_module occurs ?

scope

Comment posted on 2008/12/09 04:30:58 by boris

So is "scope" the official way to allow two different
projects (for example pliant ui pages) to use a type
Person without conflicting with each other ?

alias in module

Comment posted on 2008/12/10 02:02:28 by Hubert Tonneau

'alias' is picking a symbol in a module, and copying to another one
under a new name.
I use it to add a few symbol to already compiled modules (and to
the ones from the C written bootstraping part of Pliant).
It should be used with care because the target module will contain
some symbol that you can't find through reading the module code.
It does not overwrite because Pliant allow multiple definitions for
a same identifier, with some conflict resolution rules.

Non exported functions or variables are no more in the Pliant
compiler dictionary, so they are really hard to access.

ring_module

Comment posted on 2008/12/10 02:24:38 by Hubert Tonneau

The simplest example I can see is
/pliant/language/type/text/str32.pli
/pliant/util/encoding/utf8.pli

utf8.pli needs str32.pli to provide some functions
that use Str32 data type,

str32.pli needs utf8.pli to provide the effective code
for some casting function.

The selected solution is:
str32.pli is declared as a ring module,
defines the basic features about Str32
then includes utf8.pli
and finaly declares the casting function.

So, when utf8.pli compiles, it asks for str32.pli module,
and it would raise a circular reference between module,
but the 'ring_module' is there that says to Pliant to just
ignore it, and it works because str32.pli already
exported everything utf8.pli really needs.

scope

Comment posted on 2008/12/10 02:28:09 by Hubert Tonneau

No. 'scope' is a way to have some symbols
that are not local to a module, but local to a project.
So, they are exported, but the scope prevent a module
outside the project to import them.

With recent releases, two types with the same name in
two different modules are no more a problem.
This has been solved through assigning an index to each
module at creating time, and identifying the arguments type is
types such as Pointer:xxx Array:xxx and others using the
index instead of the type name.