c3lang / c3c

Compiler for the C3 language
https://c3-lang.org
GNU Lesser General Public License v3.0
3.02k stars 183 forks source link

Support ad hoc function and type submodule declarations #1513

Open lerno opened 1 month ago

lerno commented 1 month ago

Possible syntax

module std::foo;
// Creates a sub module std::foo::bar in which hello() is placed.
fn void bar::hello() 
{
  ...
}
Caleb-o commented 1 month ago

Assuming this only allows a single level of depth? So you can do bar::hello() but maybe not bar::baz::hello()?

lerno commented 1 month ago

Exactly, it will be limited to single level for readability and also implementation should be more straightforward.

joshring commented 1 month ago

If we look at the OOP world, do they ever create an implicit class in this sort of manner?

Personally I think a module is a fundamental organisational unit, I wouldn't want to create one implicitly, possibly by accident. I would always want to know I made an organisational unit and that I can use that further to do other interesting things with it.

I would want to be able to search for it, with module bar::new_module; to be able to find it.

Having things created without knowing we create a "mistrust" of modules as they are not concrete things I can easily look up. I feel like this would be a big missed opportunity to get people on-board with modules as something they can use and trust.

Caleb-o commented 1 month ago

@joshring I feel like this is somewhat similar to declaring nested namespaces and classes. In something like C# and Java I believe, I could make a whole lot of nested classes and namespaces. I don't think this is too different from that, except this proposal only allows for a single level of depth.

Talking about OOP, I think something like Java allows you to just make anonymous classes and such, which might go against your searchability argument. So there's definitely worse options that other languages allow and this seems pretty reasonable from what I understand.

joshring commented 1 month ago

This is still fairly explicit though:

public class Container
{
    class Nested
    {
        Nested() { }
    }
}

and you can search for class Nested

Talking about OOP, I think something like Java allows you to just make anonymous classes and such, which might go against your searchability argument. So there's definitely worse options that other languages allow and this seems pretty reasonable from what I understand.

joshring commented 1 month ago
module std::foo;
// Creates a sub module std::foo::bar in which hello() is placed.
fn void bar::hello() 
{
  ...
}

When someone searches for bar they might find it here on this line, but they don't have any idea what it is, and there is no definition elsewhere you can look up.

In this example you always know it's a nested class (even me as a non-C# dev)

public class Container
{
    class Nested
    {
        Nested() { }
    }
}
Caleb-o commented 1 month ago

I think there is one point that makes foo::bar() more obvious to what it is and that's the foo::. As far as I'm aware, the only thing that uses :: are modules. So you declare modules using this and also qualify things this way. Since this is used at the declaration, I feel like that might be more obvious than if it were to use some other pattern. So if I saw a function declaration with fn void foo::bar() {...} then I would probably assume it has something to do with modules, then eventually understand it was placed within the foo module.

There's one thing I'm not sure we've talked about though, how does this play with method syntax? (Maybe I've missed this)

A:

fn void foo::MyStruct.bar() { ... }

B:

fn void MyStruct.foo::bar() { ... }

Or is this solely for free functions?

joshring commented 1 month ago

So there is a hint that a module foo exists, but to my eyes there is no definition I would recognise for foo

Anonymous classes in Java, Polygon still exists outside as a definition I can look up

class Polygon {
   public void display() {
      System.out.println("Inside the Polygon class");
   }
}

class AnonymousDemo {
   public void createClass() {

      // creation of anonymous class extending class Polygon
      Polygon p1 = new Polygon() {
         public void display() {
            System.out.println("Inside an anonymous class.");
         }
      };
      p1.display();
   }
}

class Main {
   public static void main(String[] args) {
       AnonymousDemo an = new AnonymousDemo();
       an.createClass();
   }
}
Caleb-o commented 1 month ago

You can also do this with interfaces in Java. I think that was where I was thinking about it. You can just create anonymous classes using an interface, which you might not pick up from search unless you go looking. There's also class expressions in languages like JS, which might not be spotted, also caused by the nature of JS.

So really, my main line of thinking is that a lot of languages have weird concepts you probably won't know or understand until you come across them and do a lookup in the docs. I'm still learning concepts about languages I've used now. That's also not really a bad thing. But anyway, I don't think foo::bar() is too crazy of a concept in the grand scheme of things and :: makes it more obvious of what it could be doing and you also have to qualify the function as foo::bar() to use it.

lerno commented 1 month ago

Just as a clarification:

This thing is valid today:

fn void std::core::String.new_thing(self)
{}

And is nothing special, just an unambiguous way to write:

fn void String.new_thing(self)
{}

Note that for various reasons it doesn't matter if this new_thing method is defined in an imported module or not. It has immediate availability in all modules.

In my thinking it doesn't make sense for things like struct my_sub::AStruct { ... }, but it would be mainly for the occasional constructor function and to make converted constant enums from C APIs more friendly.

joshring commented 1 month ago

but it would be mainly for the occasional constructor function and to make converted constant enums from C APIs more friendly.

But anyway, I don't think foo::bar() is too crazy of a concept in the grand scheme of things and :: makes it more obvious of what it could be doing

I think open for extension is fine, but implicitly organising code seems like a mis-step because it can be over-used and become hard to maintain.

Not everything added is used in the way you expect is my concern.

I think one of the hallmarks of successful features in languages is easily being able to find the definitions of concepts in the code. That's why things like C/C++ macros are so hard, because they can nest and you don't know where the code is, leading to great complexity.

Perhaps not as bad as that, but could be easily misused, leading to spaghetti code from less experienced C3 programmers (which will be the majority for a while as C3 is new).