Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement namespaces #388

Merged
merged 15 commits into from
Feb 5, 2024
Merged

Implement namespaces #388

merged 15 commits into from
Feb 5, 2024

Conversation

b-studios
Copy link
Collaborator

@b-studios b-studios commented Feb 3, 2024

The purpose of this PR is to experiment with an implementation of namespaces.

The design is supposed to be fully backwards compatible (for now) while preparing for additional future extensions such as those listed in #389

For a discussion on the design also see: https://gist.github.com/jiribenes/8195243f56a454d3465b06f02e8bb5a2?permalink_comment_id=4872857#gistcomment-4872857

@b-studios
Copy link
Collaborator Author

The current state of the implementation already allows parsing

def main() = {
  def foo() = effekt::println("foo")

  main::foo();

  // explicit
  val l2: List[effekt::Int] = effekt::list::Cons(1, effekt::list::Cons(2, effekt::list::Nil()))
  effekt::list::map(l2) { x => effekt::infixAdd(x, 1) }
}

but namer simply ignores the qualifiers. This way all existing programs are (still) fully backwards compatible.

@b-studios
Copy link
Collaborator Author

The following example now works:

namespace internals {
  def add(n: Int, m: Int) = nested::helper(n, m)

  namespace nested {
    def before() = nested::increment(1)
    def increment(n: Int): Int = internals::add(n, 1)
    def incrementAlternative(n: Int) = internals::nested::increment(n)

    def helper(n: Int, m: Int): Int = n + m
  }
}

def main() = {
  println(internals::nested::increment(7))
  println(internals::nested::incrementAlternative(8))
}

@b-studios
Copy link
Collaborator Author

b-studios commented Feb 4, 2024

Basic namespacing now already works.

What could be done in the future is:

  • to control exporting selectively using visibility modifiers. Technically this could be implemented by annotating bindings with the modifier
    case class Bindings[F[_]](
      terms: Map[String, Set[F[TermSymbol]]], // terms can be overloaded
      types: Map[String, F[TypeSymbol]],
      captures: Map[String, F[Capture]],
      namespaces: Map[String, Bindings[F]]
    )
    
    enum Visibility[T] {
      case Public(binding: T)
      case Private(binding: T)
    
      def binding: T
    }
    
  • to separate include and import and also allow more fine-grained imports, i.e., import foo::bar::{ x, y }, renaming imports etc.
  • add export foo for reexporting, again potentially with renaming
  • add a full project mode where all (specified) files are always processed so includes are not necessary. Such a project mode could also configure other flags to the Effekt compiler, as well as declare dependencies etc.

@b-studios
Copy link
Collaborator Author

b-studios commented Feb 4, 2024

Turns out that allowing import in arbitrary position can be quite difficult to implement. For now, I would only allow imports at the top of the file (before the first definition) and maybe later in statement position. Problem is the interaction with preresolve.

Also, namespaces are only allowed to contain definitions (that is no statements).

@b-studios b-studios marked this pull request as ready for review February 4, 2024 22:23
@b-studios
Copy link
Collaborator Author

I am sufficiently happy with the implementation now and would be willing to merge (we can always still bike-shed) syntax, later (this is trivial to change).

Maybe you can take a look at it once more (especially the tests to get an impression of what is supposed to be working)?

@jiribenes
Copy link
Contributor

def main() = {
  def foo() = effekt::println("foo")

  main::foo();

  // explicit
  val l2: List[effekt::Int] = effekt::list::Cons(1, effekt::list::Cons(2, effekt::list::Nil()))
  effekt::list::map(l2) { x => effekt::infixAdd(x, 1) }
}

Just to clarify, what introduces a new namespace implicitly?

According to the above, it seems like a new function definition does (main::foo). Do types introduce new namespaces too (MyList::MyCons)? 🤔

@b-studios
Copy link
Collaborator Author

b-studios commented Feb 5, 2024

Edit: I now recognize that the example is my example for above... that was only the parser! This is not how namespaces are implemented.

A function introduces a new (anonymous) namespace-root.

That is

def somefunction() = {

  namespace nested {
    def go() = println("hello")
  }

  nested::go()
}

is valid.

But all namespaces in the function will be flattened out and definitions become function-local definitions.

To be more precise, if we use ::foo to be an absolute path from a namespace root, then in somefunction, we could in principle (not implemented) refer to go as ::nested::go(), however there is also the lexically enclosing root on the module level, so in ::somefunction the root refers to the outer one.

We could explicitly distinguish between these roots (ie., somefunction::nested::go), but I don't see a good reason for now.

@b-studios
Copy link
Collaborator Author

b-studios commented Feb 5, 2024

I am merging this for now since I want to merge the implementation anyways, modulo bugs and future changes of the design (which should be based on top of this).

Let's revisit it in the next Effekt group meeting and potentially bike-shed / and redesign things more.

@b-studios b-studios merged commit d8a1fba into master Feb 5, 2024
1 check passed
@b-studios b-studios deleted the feature/namespaces branch February 5, 2024 17:16
@b-studios b-studios restored the feature/namespaces branch February 5, 2024 17:16
@b-studios b-studios deleted the feature/namespaces branch February 5, 2024 17:17
@b-studios b-studios mentioned this pull request Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants