Giter Site home page Giter Site logo

Proposal: Module classes about wren HOT 14 CLOSED

wren-lang avatar wren-lang commented on July 28, 2024
Proposal: Module classes

from wren.

Comments (14)

pwoolcoc avatar pwoolcoc commented on July 28, 2024

I like #2 the best, but I am thinking, won't we have to add the ability to nest classes if we do this? I think people will want to do something like the following, especially if they are coming from ruby:

// other.wren
module Other {
    class DoWork {
        doTheWork {
        }
    }
}
// main.wren
var other = IO.import("other")

module Main {
    static inMain {
        var a = other.DoWork.new
        a.doTheWork
    }
}

Right now trying to nest classes gives a syntax error.

If the answer is, "don't nest classes," then ok, but I am curious how/whether it will be allowed to export more than one class from a module.

from wren.

munificent avatar munificent commented on July 28, 2024

Even without explicit support for nested classes, you could still do this, like so:

class A {}
class B {}

module Both {
  static a { A }
  static b { B }
}

It's definitely kind of ugly.

I have thought about nested classes for a while, and in fact about more directly making modules equivalent to classes. That's one of the key ideas in Gilad Bracha's Newspeak and I think it's got a lot going for it. It also lines up well with Wren's heavily class-based philosophy.

The natural endpoint of that line of thinking is that the top level of a module just is a class. I've considered that a little bit, but I haven't spent the time to sit down and think it all the way through to see if it makes sense.

One thing it implies is that "top-level" variables are properties on that outermost class, which means they're dynamically dispatched. That handles things like mutual recursion at the top level well (which currently isn't handled by Wren), but it means giving up errors on typos for top-level names.

I'll try to spend some time working through the ramifications and see if that makes sense.

from wren.

EvanHahn avatar EvanHahn commented on July 28, 2024

Why can't the execution model for module classes work for the Lua-style method? I personally like the Lua style better than module classes, for what it's worth.

from wren.

munificent avatar munificent commented on July 28, 2024

Lua doesn't support cyclic requires at all. You can sort of work around it, but it's pretty hacky. I'm hoping for something that works a little more gracefully than that.

from wren.

kmarekspartz avatar kmarekspartz commented on July 28, 2024

Should alternate Strawmen go here or as separate issues? I've got a header file proposal, inspired by SML modules/structures/signatures/functors.

from wren.

munificent avatar munificent commented on July 28, 2024

Should alternate Strawmen go here or as separate issues?

Yes, that would be great!

I've got a header file proposal, inspired by SML modules/structures/signatures/functors.

I'm definitely intrigued.

from wren.

kmarekspartz avatar kmarekspartz commented on July 28, 2024

Would you be able to have private methods under this proposal? Without a keyword, that is.

from wren.

munificent avatar munificent commented on July 28, 2024

The module can definitely have private variables. Variables at the top level are still lexically scoped to the module and wouldn't be exported.

Wren doesn't have any concept of private methods, though, so if you add any methods to the module class itself, they'd be private.

from wren.

willowless avatar willowless commented on July 28, 2024

What about a late binding model. Instead of a piece of code specifying what it needs, its needs are known at compile time to bytecode. It could be compiled within a 'scope' that was defined when the code was loaded from a main,

new Scope("Vector", "Matrix", "HobermanSphere");
new Scope("subproject/*")

Wren loads up all the named stuff, with wildcards since as you said, logical names work best and then runs the code in that 'scope'. Fibers can therefore have different scopes entirely or a scope can be reused across fibers if appropriate.

HobermanSphere does not need to specify how or where it gets Vector and Matrix from, it just assumes there will be classes with those names visible in its scope, otherwise it'll get a runtime error when the scope tries to do the final binding. As such, you'd know 99% of the time if you were missing a library the moment you try to run your program.

Thought it was worth offering the idea of flipping the module discussion on its head.

from wren.

munificent avatar munificent commented on July 28, 2024

I'm sorry, but I had trouble following that. Can you walk me through how you envision it loading a couple of modules?

from wren.

willowless avatar willowless commented on July 28, 2024

Consider each Scope instance like a Smalltalk 'mini image'. You define a scope and give it (several) files to load. At the end of this it will bind the globals for that scope. An example main might look like this:

// project/geometry/Vector.wren
class Vector2D {
   new(x, y) {
    _x = x
    _y = y
   }
   x { return _x }
   y { return _y }
   to(end) {
     return new LineSegment(this, end)
   }
}

// project/geometry/LineSegment.wren
class LineSegment {
    new(start, stop) {
     _start = start
     _stop = stop
    }
    start { return _start }
    stop { return _stop }
    direction { return _stop - _start }
    lengthSquared { return this.direction.x * this.direction.x + this.direction.y * this.direction.y }
    length { return this.lengthSquared.sqrt }
    midpoint { return (this.length / 2) + _start }
    excuseForCircularDependency { return new Vector2D(_start.x, _start.y) }
}

// main.wren
var mainProject = new Scope("project/**")
mainProject.call {
    var unitLength = new Vector2D(0, 0).to(Vector2D(1, 0)).length
    IO.print(unitLength)
}

Because the Scope is given file pattern(s) to determine what to load, you can replace any individual file with your own variant at any point, when it would be most advantageous. Otherwise, within a scope, all things are considered 'global' to that scope.

This would double as not only the way to 'include' code, but also the scope that code is resolved in.

// more main.wren
var experimentalProject = new Scope("project/geometry/LineSegment", "experiment/superFastVector2D.wren")
experimentalProject.call {
    var unitLength = new Vector2D(0, 0).to(Vector2D(1, 0)).length
    IO.print(unitLength)
}

Here LineSegment is the same code from both scopes, but the experimentalProject and mainProject use a different Vector2D. This allows the top-level to specify what is and is not in a scope. In this scenario I can compare how the two different implementations of Vector2D compare. They cannot both be loaded in to the same scope at once however, that'd be a duplicate binding error.

from wren.

munificent avatar munificent commented on July 28, 2024

Ah, I think I understand it more now, thank you!

var mainProject = new Scope("project/**")

Is the idea here that it would load everything in that directory? Or just that if it needs something, it looks there? If the latter, how does it determine what's actually needed?

I do like the idea of being able to separate how an import is physically located from the logical identifier that's being requested.

Currently, I'm working on implementing #122 instead of this proposal. Since it makes imports statements, I believe we could retrofit that same idea there if needed.

from wren.

willowless avatar willowless commented on July 28, 2024

Personally, I'd just have it load everything in the list. If Wren supports the ability to add new methods to an existing class, then this'd be useful. You could lazily compile the class contents so that the identifiers are all known, but would that really save much time? you do boast a fast single-pass compiler for Wren after all.

One thing that is missing from this proposal is a way to initialise code based on other code. Having a scope-level event of "Everything is now loaded, you may initialise" on a per-class (or per-module) basis would be required, eg:

// FunkyGraphics.wren
OpenGL.loaded {
...init resources...
}

This'd be the only explicit dependency needed though.

from wren.

munificent avatar munificent commented on July 28, 2024

Closing this because modules are in now! I ended up going with a slightly different approach since I realized while implementing that it's easier to work with a module's top-level variables as variables instead of trying to make something like a first-class modules.

from wren.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.