Search code examples
importread-eval-print-looprascal

Rascal: Containing Imported Symbols


I work with multiple grammars in the repl. The grammars use same names for some of their rules.

One of the documentation recipes mentions full qualification, to disambiguate type annotations in function pattern matching (it's in a note of the load function, but not in the code of this page - the .jar has it correct). But that might become tedious, so maybe there is aliasing for imports (like in Python import regex as r)?! And using full qualification in the first argument of the parse function doesn't seem to help to disambiguate all parse rules that are invoked recursively, parse(#lang::java::\syntax::Java18::CompilationUnit, src). At least it produces weird errors if I also import lang::java::\syntax::Java15.

In general, what is a safe way to handle symbols from different modules with same names?

Alternatively, is there a way to "unload" a module in the repl?


Solution

  • Some background information:

    1. Rascal modules are open for reasons of extensibility, in particular data, syntax definitions and overloaded functions can be extended by importing another module; In this way you can extend a language and its processing functions by importing another module and adding rules and function alternatives at leisure.
    2. There is a semantic difference between importing and extending a module. In particular import is not transitive and fuses only the uses of a name inside the importing module, while extend is transitive and also fuses recursive uses of a name in the module that is extended. So for extending a language, you'd default to using extend, while for using a library of functions you'd use import.
    3. We are planning to remove the fusing behavior from import completely in one of the releases of 2020. After this all conflictingly imported non-terminal names must be disambiguated by prefixing with the module name, and prefixing will not have a side-effect of fusing recursively used non-terminals from different modules anymore. Not so for extend, which will still fuse the non-terminal and functions all the way.
    4. all the definitions in a REPL instance simulate the semantics of the members of a single anonymous module.

    So to answer your questions:

    • it's not particularly safe to handle symbols from different imported modules with the same name, until we fix the semantics of import that is.
    • the module prefix trick only works "top-level", below this the types are fused anyway because the code which reifies a non-terminal as a grammar does not propagate the prefix. It wouldn't know how.
    • Unimporting a module:

      rascal>import IO;
      ok
      rascal>println("x");
      x
      ok
      rascal>:un
      undeclare   unimport    
      rascal>:unimport IO
      ok
      rascal>println("x");
      |prompt:///|(0,7,<1,0>,<1,7>): Undeclared variable: println
      
      • probably one of the least used features in the environment; caveat emptor!

    To work around these issues, a way is to write functions inside a different module for every separate language/language version, and create a top module which imports these if you want to bundle the functionality in a single interface. This way, because import is not transitive, the namespaces stay separate and clean. Of course this does not solve the REPL issue; the only thing I can offer there is to start a fresh REPL for each language version you are playing with.