~icefox/garnet#8: 
Thoughts on modules

It would be surprisingly appealing if there were no methods, traits, or anything like that... just data structures, functions and namespaces/modules. Look at OCaml's modules for inspiration perhaps.

It didn't start related, but various people on the unofficial Rust #lang-dev discord pointed out that types and modules can be connected, and this is kinda what OCaml does. So then you start thinking about generics and templates as an offshoot of your module system.

Data is data. Functions change data. Modules are collections of functions and data. Now... if functions are also data, and modules are also data (and ergo functions), then life gets Interesting. Lua and OCaml both do some of this metaprogramming. Especially if types are also data, then you can write a function specialize(module: M<?>, type: T> -> M<T>.

And a rust-style trait becomes a compile-time function to check whether a type fulfills its constraints, trait_applies(module: M<?>, type: T) -> Result<M<T>, CompileError>.

mmmmman, and all this came out of the thought of just "what if I didn't have methods?"

Status
REPORTED
Submitter
~icefox
Assigned to
No-one
Submitted
1 year, 3 months ago
Updated
4 months ago
Labels
T-THOUGHTS

~icefox 1 year, 3 months ago

This and the previous posts in that series are really interesting. Learn more about SML and OCaml's modules. As far as I'm concerned, having modules be the compilation unit and modules form a DAG is just fine, but there's space to wiggle here.

~icefox 5 months ago

So, thinking about modules more. What is a module? It's a collection of names and values grouped together. Okay then, how is it different from a struct? A struct is a collection of names and values grouped together.

Why are modules a special case? First, so our linker knows how to find symbols and, instead of having a layer of indirection, "snap" it to a direct reference. Instead of some_module.foo() saying load r1, some_module; add r1, offset_of_foo; call r1; it can just become call (some_module + offset_of_foo); and it's the linker's job to do the math and fill things in. Dynamic libraries basically have that level of indirection already anyway, IIRC, but with static linking cutting it out is an easy win. This sounds like an implementation detail! The second reason modules are a special case is 'cause there's only ever one instance of a module; they're singletons. ...Or ARE they? OCaml and Zig(???) say otherwise!

So, I don't see a reason that we can't have modules and structs be basically the same thing, if we have a Sufficiently Clever Compiler/Linker. Doing import foo; then turns into static foo: FooModule = something magic in some other file or compilation unit;, while mod foo { fn bar(): i32 = ... end } turns into struct foo { bar: fn(): i32 }; You may want to be able to do things like import foo::bar; and then get bar in your local namespace, but that's purely syntactic sugar and frankly may (somehow) apply equally well to structs as well as modules, similar to Rust's enums. (I feel like Odin has some prior art there too?)

However, the start of this isn't QUITE correct. Modules have more than just values/functions in them, they also contain types. Types CAN have some actual code representation in the form of RTTI/reflection stuff, but sometimes a type is more than just a vtable. What is a type? Well you say let x: i32 = foo, the type is the i32. So it is something that the compiler knows about as a special case. Does it NEED to be, though? A type is a memory size, a set of functions (which may be a vtable or may be known statically), and some properties (marker traits, whatever) such as Rust's Sync or Copy or such. Right now, let's just say that those continue to be a special case as we think about this.

~icefox 5 months ago*

Flip side, idk why you wouldn't be able to attach arbitrary types to structs, at least conceptually. Rust has associated consts on structs already, and apparently has an RFC for associated types: https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md with associated tracking ticket at https://github.com/rust-lang/rust/issues/8995 . It just... you know, has been open since 2013.

Zig also does essentially this, with more compile-time-type-wizardry stuff. OCaml also does something kinda similar, upon inspection, though it makes it more complicated for some reason. So I think that module == struct is actually not a terrible assumption to start with.

To investigate: https://people.mpi-sws.org/~rossberg/1ml/

~icefox referenced this from #18 5 months ago

~icefox 4 months ago

Okay, it turns out that 1ML might be exactly what we want. Good introductory talk: https://www.youtube.com/watch?v=42Wn-mXWcms

Register here or Log in to comment, or comment via email.