discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Re: Making modules first class without breaking existing Openscad code

JB
Jordan Brown
Fri, Sep 2, 2022 7:06 PM

[ Merged a couple of replies ]

On 9/1/2022 11:33 PM, Andy Little via Discuss wrote:

x = module television(55,2);

What are the "members" of x?

[ television()'s local variables ]

So far I understand what you're saying.

When would the echo() happen?

x();

That means that you're evaluating the assignments, but not evaluating
the module invocations.

Which, given OpenSCAD's already ... interesting ... evaluation
semantics, is not that shocking, but I don't think there's a direct
parallel anywhere else, of evaluating a module's assignments without
then evaluating its module invocations.

Even that partial evaluation couldn't work with arguments.  If you did

m = module (size) television(size, 2);

then there's no way for (say) m.ratio to have a value yet, because
nothing has supplied the size.

Side trip: especially when we bring geometry-as-data into the picture,
we need to be very careful what words like "instantiate" mean.  Once we
have geometry-as-data, it could mean two very different things:  it
could mean evaluating the module so as to generate geometry data, or it
could mean that plus adding that geometry to the model being
constructed.  I would call the first "evaluation" (with a sub-step of
"rendering"), and the second "adding to the model" or, more correctly,
"adding to the CSG tree".  (The latter two differ if the operation is
itself in the context of constructing geometry-as-data.)  Today,
evaluating a module always adds its geometry to the model; with
geometry-as-data that's not always the case.

https://github.com/openscad/openscad/pull/3956 (rendering geometry
into data)

fun and potentially very useful but doesn’t really interact with the
module_alias since it transfoms a module_instantiation into pure data.
A quick read of the implementation suggest it instantiates module(s)
and then removes them from the AST. That might be a little hairy!

I haven't looked at the implementation, but removing them from the AST
wouldn't make sense.  The AST is a representation of the program, not of
the geometry.  Perhaps you mean the CSG tree?

m = module(parameters) {...};

m is a reference to that module, can be invoked like so:

m(arguments);

looks like an alternative syntax for defining modules?

Yes, just as

f = function (args) ...;

is an alternative syntax for defining functions in the function-literals
mechanism.

Critically, both put references to the module (or function) into a
variable, where you could call them later.

but how do you refer to already defined modules?

Do you mean "how do you have m invoke an already-defined module?"?

m = module (size) { television(size, 2); }

Do you mean "how do you make m be a reference to a built-in module or to
a module defined using the existing module syntax?"?

You can't, just as you can't use the function-literals mechanism to get
a reference to a built-in function or a conventionally-defined function.

In both cases there's a namespace problem.

cube = 5;
x = cube;

is totally legal, and yields x=5.  If "x = cube" could get a reference
to the builtin cube module, it would be ambiguous whether it should do
that, or should get the 5 from the variable named "cube".

This is analogous to the invocation namespace problem.

We might eventually bite that bullet too, but it's a compatibility and
confusion concern.

you could probably extend the module_alias syntax to parameters

m = module(height) cube([1,1,height]);

m(20); // --> cube([1,1,20]);

And here we're getting close to aligned.  This is listed as a possible
alternative syntax in my Google doc.

But I suspect that we would differ on what you can then "see" when you
look at m.

I would say ... nothing.  The module (consisting of one invocation of
cube()) has not yet been evaluated, and so there are no values to see.

Perhaps more fundamentally, I would say that m is a reference to an
anonymous module, and the body of that anonymous module invokes cube(). 
I think that maybe you are thinking that m would refer to cube itself.

syntactically you can do all the following /without instantiating the
module/

|m = module name(args); // a module_alias without parameters|

|m2 = module(params) name(args); // a module_alias with parameters |

|m3 = module(params) { inner_input }; // an anonymous module referred
to by the module_alias m3|

I don't think I would allow the first.  Module definitions (and function
definitions) always require a parameter specification; I would not
change that for this feature.  Also, in JavaScript you're allowed to
give a name for an inline function, e.g. "f = function foo() { ... };".
I believe that name is only visible inside that function body, making
it only marginally useful to the program, but it is also visible in
stack backtraces and so is helpful in diagnosis.  I suspect that we may
eventually want to allow names in our function-literal and
module-literal syntax, also for diagnostic purposes.  Doing so would
make that first be ambiguous:  is it an anonymous module that invokes
"name" with args, or is it a module named "name", with args, with an
empty body?

For the second, the question is what m2 refers to.  I would say that m2
is a reference to an anonymous module, and the body of the anonymous
module is an invocation of name(args).  I would say that it is exactly
equivalent to

m2 = module(params) { name(args); };

just as these two are exactly equivalent:|
|

module xxx(size) cube(size);
module xxx(size) { cube(size); };
[ Merged a couple of replies ] On 9/1/2022 11:33 PM, Andy Little via Discuss wrote: >> x = module television(55,2); >> >> What are the "members" of x? >> > [ television()'s local variables ] So far I understand what you're saying. >> When would the echo() happen? > x(); That means that you're evaluating the assignments, but not evaluating the module invocations. Which, given OpenSCAD's already ... interesting ... evaluation semantics, is not *that* shocking, but I don't think there's a direct parallel anywhere else, of evaluating a module's assignments without then evaluating its module invocations. Even that partial evaluation couldn't work with arguments.  If you did m = module (size) television(size, 2); then there's no way for (say) m.ratio to have a value yet, because nothing has supplied the size. Side trip: especially when we bring geometry-as-data into the picture, we need to be very careful what words like "instantiate" mean.  Once we have geometry-as-data, it could mean two very different things:  it could mean evaluating the module so as to generate geometry data, or it could mean that *plus* adding that geometry to the model being constructed.  I would call the first "evaluation" (with a sub-step of "rendering"), and the second "adding to the model" or, more correctly, "adding to the CSG tree".  (The latter two differ if the operation is itself in the context of constructing geometry-as-data.)  Today, evaluating a module always adds its geometry to the model; with geometry-as-data that's not always the case. >> https://github.com/openscad/openscad/pull/3956 (rendering geometry >> into data) > > fun and potentially very useful but doesn’t really interact with the > module_alias since it transfoms a module_instantiation into pure data. > A quick read of the implementation suggest it instantiates module(s) > and then removes them from the AST. That might be a little hairy! > I haven't looked at the implementation, but removing them from the AST wouldn't make sense.  The AST is a representation of the program, not of the geometry.  Perhaps you mean the CSG tree? >> m = module(parameters) {...}; >> >> m is a reference to that module, can be invoked like so: >> >> m(arguments); >> > looks like an alternative syntax for defining modules? Yes, just as f = function (args) ...; is an alternative syntax for defining functions in the function-literals mechanism. Critically, both put references to the module (or function) into a variable, where you could call them later. > but how do you refer to already defined modules? Do you mean "how do you have m invoke an already-defined module?"? m = module (size) { television(size, 2); } Do you mean "how do you make m be a reference to a built-in module or to a module defined using the existing module syntax?"? You can't, just as you can't use the function-literals mechanism to get a reference to a built-in function or a conventionally-defined function. In both cases there's a namespace problem. cube = 5; x = cube; is totally legal, and yields x=5.  If "x = cube" could get a reference to the builtin cube module, it would be ambiguous whether it should do that, or should get the 5 from the variable named "cube". This is analogous to the invocation namespace problem. We might eventually bite that bullet too, but it's a compatibility and confusion concern. > you could probably extend the module_alias syntax to parameters > > m = module(height) cube([1,1,height]); > > m(20); // --> cube([1,1,20]); > And here we're getting close to aligned.  This is listed as a possible alternative syntax in my Google doc. But I suspect that we would differ on what you can then "see" when you look at m. I would say ... nothing.  The module (consisting of one invocation of cube()) has not yet been evaluated, and so there are no values to see. Perhaps more fundamentally, I would say that m is a reference to an anonymous module, and the body of that anonymous module invokes cube().  I think that maybe you are thinking that m would refer to cube itself. > syntactically you can do all the following /without instantiating the > module/ > > |m = module name(args); // a module_alias without parameters| > > |m2 = module(params) name(args); // a module_alias with parameters | > > |m3 = module(params) { inner_input }; // an anonymous module referred > to by the module_alias m3| > I don't think I would allow the first.  Module definitions (and function definitions) always require a parameter specification; I would not change that for this feature.  Also, in JavaScript you're allowed to give a name for an inline function, e.g. "f = function foo() { ... };". I believe that name is *only* visible inside that function body, making it only marginally useful to the program, but it is *also* visible in stack backtraces and so is helpful in diagnosis.  I suspect that we may eventually want to allow names in our function-literal and module-literal syntax, also for diagnostic purposes.  Doing so would make that first be ambiguous:  is it an anonymous module that invokes "name" with args, or is it a module named "name", with args, with an empty body? For the second, the question is what m2 refers to.  I would say that m2 is a reference to an anonymous module, and the body of the anonymous module is an invocation of name(args).  I would say that it is exactly equivalent to m2 = module(params) { name(args); }; just as these two are exactly equivalent:| | module xxx(size) cube(size); module xxx(size) { cube(size); };
K
kwikius@yahoo.com
Sat, Sep 3, 2022 12:53 AM

Hi again Jordan,

Thanks for the continued input. It is late and I will look in more detail at your post tomorrow.

For now, accessing the member assignments of a module_alias with parameters without instantiation into the CSG tree is just a case of reusing the same syntax that you can use to access the assignments of a module:

module television(size, b) { diagonal = sqrt( size.x^2 + size.y^2);}

diagonal = module m([30,20]).diagonal;  // an anonymous module_alias
m = module (size) television(size, 2);  // named alias

diagonal1 = module m([30,20]).diagonal;
Hi again Jordan, Thanks for the continued input. It is late and I will look in more detail at your post tomorrow. For now, accessing the member assignments of a module_alias with parameters without instantiation into the CSG tree is just a case of reusing the same syntax that you can use to access the assignments of a module: module television(size, b) { diagonal = sqrt( size.x^2 + size.y^2);} ``` diagonal = module m([30,20]).diagonal; // an anonymous module_alias ``` ``` m = module (size) television(size, 2); // named alias diagonal1 = module m([30,20]).diagonal; ```
K
kwikius@yahoo.com
Sat, Sep 3, 2022 5:58 AM

Aoilogies. The example I showed in the last post was unclear.  The module_alias can be treated like a module so the same syntax can be used:

module television(size, b) { diagonal = sqrt( size.x^2 + size.y^2);}
diagonal = module television([30,20]).diagonal;  // access using an anonymous module_alias
m = module (size) television(size, 2); // named module_alias with params
diagonal1 = module m([30,20]).diagonal;  // access using named module_alias
m1 = module m([30,20]);    // module_alias referring to another module_alias
diagonal2 = m1.diagonal   // access to module_alias via another module_alias

We can also do comparisons

assert ( module television([30,20],2) == module television([30,20],2));
assert ( module m([30,20]) == module television([30,20],2) );
assert ( m1 == module television([30,20],2) );
assert ( module m1 == module television([30,20],2) );  // redundant syntax on module_alias without params but ok
assert ( m != module m([1,1]) );
Aoilogies. The example I showed in the last post was unclear. The module_alias can be treated like a module so the same syntax can be used: ``` module television(size, b) { diagonal = sqrt( size.x^2 + size.y^2);} ``` ``` diagonal = module television([30,20]).diagonal; // access using an anonymous module_alias ``` ``` m = module (size) television(size, 2); // named module_alias with params ``` ``` diagonal1 = module m([30,20]).diagonal; // access using named module_alias ``` ``` m1 = module m([30,20]); // module_alias referring to another module_alias ``` ``` diagonal2 = m1.diagonal // access to module_alias via another module_alias We can also do comparisons assert ( module television([30,20],2) == module television([30,20],2)); assert ( module m([30,20]) == module television([30,20],2) ); assert ( m1 == module television([30,20],2) ); assert ( module m1 == module television([30,20],2) ); // redundant syntax on module_alias without params but ok assert ( m != module m([1,1]) ); ```
K
kwikius@yahoo.com
Sat, Sep 3, 2022 6:08 AM

last example should be

assert ( m1 != module m([1,1]) );

hopefully at some point I will be able to run these examples before posting!
last example should be ``` assert ( m1 != module m([1,1]) ); hopefully at some point I will be able to run these examples before posting! ```
K
kwikius@yahoo.com
Sat, Sep 3, 2022 6:36 AM

x = module television(55,2);

x();

That means that you're evaluating the assignments, but not evaluating

the module invocations.

The side effects of

x();

should be exactly the same as the side effects of

television(55,2);

Otherwise it is going to be tedious instantiating module_aliases

Even that partial evaluation couldn't work with arguments.  If you did

m = module (size) television(size, 2);

then there's no way for (say) m.ratio to have a value yet, because

nothing has supplied the size.

hopefully covered by previous post.  You would do

r = module m([1,2[]).ratio;

We have to do it this way according to the gramma since

r = m([1,2]).ratio;

would means invoke the function m([1,2]) and then evaluate the ratio member of the resulting value.

> > x = module television(55,2); > > > > x(); > > That means that you're evaluating the assignments, but not evaluating > > the module invocations. The side effects of ``` x(); ``` should be exactly the same as the side effects of ``` television(55,2); ``` Otherwise it is going to be tedious instantiating module_aliases > Even that partial evaluation couldn't work with arguments. If you did > > m = module (size) television(size, 2); > > then there's no way for (say) m.ratio to have a value yet, because > > nothing has supplied the size. hopefully covered by previous post. You would do r = module m(\[1,2\[\]).ratio; We have to do it this way according to the gramma since r = m(\[1,2\]).ratio; would means invoke the function m(\[1,2\]) and then evaluate the ratio member of the resulting value.