[ 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); };
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;
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]) );
last example should be
assert ( m1 != module m([1,1]) );
hopefully at some point I will be able to run these examples before posting!
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.