On Aug 24, 2025, at 4:17 PM, Jordan Brown via Discuss discuss@lists.openscad.org wrote:
And if we try to do everything, that's what we'll get. I think that this is close to the limit of what we want to implement; I'm not sure that a super mechanism makes the cut, especially if it is clearly possible to DIY.
Not sure that super() is even needed.
base = object(
size=[1,2,3],
Foo = function(x) ...,
...
);
subclass = object(
base,
super_Foo = base.foo,
Foo = function(x)
/* do something else here */
this.super_Foo(x),
...
)
On 8/25/2025 2:23 AM, Cory Cross wrote:
Should we rename the data type, and preserve that different name forever?
JavaScript objects have many pitfalls when used as a key-value store
https://web.archive.org/web/20150729173703/http://speakingjs.com/es5/ch17.html#_pitfalls_using_an_object_as_a_map.,
There's an assertion like that in the comments in our source tree at
work, and I've never understood it.
Yes, of course objects inherit their prototype's members. That's kind
of the point of prototypes.
If you didn't want the object to inherit the prototype's members... when
why the heck did you give it a prototype?
If you try to jam new members onto Object itself, so that they are
inherited by every object, you get what you deserve.
so anyone used to JavaScript in the last decade would be used to Map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map.
So, yes, use a proper name for key-value store if that's the primary
goal. And let Objects be for Object-Oriented programming.
So you're in favor of two different data types.
That seems to me like a horrible answer for a language of this class.
Except that familiarity is instead misleading and confusing, because
you can't do the majority of things you actually do in JavaScript with
the proposed OpenSCAD object.
So we shouldn't use braces for grouping, because it isn't C? We
shouldn't use array[index] because you can't assign to it?
I'd say that a majority of JS usage of objects can be summarized as:
and we can do those things. (Especially once we have the OEP8a /
PR#6081 support for {} creation.
OpenSCAD has immutable values. That makes it different from any other
mainstream language. So we have three choices:
I know which of those I would pick.
BTW: We can't use Map either, because Map relies on being mutable;
there's no way to put anything into it at creation time.
It seems your definition of "simple" only applies to neophyte users of
OpenSCAD and isn't intended to make it simpler for power users. I'd
like it to be simple for both.
There's truth to that. I believe that the user base for OpenSCAD is
more slanted towards the non-programmer than the user base for most
languages is, and that even with programmers it's more slanted toward
smaller projects than most languages are. I expect that my house model,
at ~10KLOC, is a large project, and that BOSL2, at ~80KLOC, is one of
the largest, if not the largest, OpenSCAD projects in the world.
I expect that people who consume non-trivial objects will outnumber
those who produce them, by a favor of a hundred to one or more.
On 8/24/25 4:17 PM, Jordan Brown via Discuss wrote:
My suggestion is that we should not add any OO-like features to the
key-value store unless we've thought through the design.
Should we back out the existing features, because they are "incomplete"?
Should we rename the data type, and preserve that different name forever?
Actually, you got me thinking. textmetrics and fontmetrics are still
experimental and not in a release; json import is not in a release.
So, yes, rename object to map or dict or struct or json.
JsonCPP calls it a Json::Value
http://open-source-parsers.github.io/jsoncpp-docs/doxygen/class_json_1_1_value.html.
Python reads them to a dictionary
https://docs.python.org/3.9/library/json.html.
Java Jackson reads to a JsonNode
https://mkyong.com/java/jackson-how-to-parse-json/.
Of the top 4 popular languages, only JavaScript calls them objects
because it's where they originated.
Golang unmarshals them to a Struct https://go.dev/blog/json.
https://docs.python.org/3.9/library/json.htmlElixir decodes json to
native types https://hexdocs.pm/elixir/main/JSON.html and associative
data decodes to maps
https://hexdocs.pm/elixir/main/keywords-and-maps.html.
I’m using the new object() function pretty heavily. I’ve always used LOTS of variables and the identifiers get very long.
I find the scope of enumerating identifiers very frustrating, in that you can’t reference a previously declared value in the same definition.
for example:
you cant do:
areaOf5 = object(
pi = 3.142,
r = 5,
area = pi * pow(r,2)
);
the compiler will throw an “unknown variable” error when you use pi in the area definition.
There is a work around however. If you wrap a function definition around the area
declaration and use the “let(“ syntax to declare temporary values then you’re free of this restriction.
e.g.
function areaOf5() =
let(
pi =3.142,
)
object(
r = 5,
area = pi * pow(r,2)
)
In this example the definition of pi is ‘lost’ as a member of the object when the function is called, but you can include it by declaring it’s “let” value in the object definition.
e.g.
function areaOf5() =
let(
pi =3.142,
)
object(
pi = pi
r = 5,
area = pi * pow(r,2)
)
You can even do fancy stuff like:
function areaOf5() =
let(
const = object(
pi =3.142,
ftPerMile = 5280,
cToF = 5/9
miles = 7,
totalFeet = miles * ftPerMile,
r = 5,
),
c = const
)
object(
const,
area = c.pi * pow(c.r,2)
area = pi * pow(r,2)
)
So to my unsophisticated thinking; an object definition is semantically quite similar to a function, and syntactically even more similar, how much of an effort would it be to add “let()” functionality to the new object function and allow for some very valuable efficiencies in declaring objects that need to enumerate values that may share references to the same values. YES, there is a workaround, but the coding with those workarounds can get increasingly obscure. I think anyone who has used the new object() function in any more than checking out out briefly may well share the frustration I’ve been encountering.
If added, then an object function could be formed like this:
areaOf5 = object(
let(
pi =3.142
)
r = 5,
area = pi * pow(r,2)
);
Thoughts and comments appreciated… especially from whomever did the fantastic job of developing and including this object function to begin with!
Thanks
Hugo
Wow. I agree that the initial "naive" approach should work, without all
of the complex alternatives that you describe. How feasible this may be
is beyond my understanding .
Jon
On 10/24/2025 4:20 AM, Hugo Jackson via Discuss wrote:
I’m using the new object() function pretty heavily. I’ve always used LOTS of variables and the identifiers get very long.
I find the scope of enumerating identifiers very frustrating, in that you can’t reference a previously declared value in the same definition.
for example:
you cant do:
areaOf5 = object(
pi = 3.142,
r = 5,
area = pi * pow(r,2)
);
the compiler will throw an “unknown variable” error when you use pi in the area definition.
There is a work around however. If you wrap a function definition around the area
declaration and use the “let(“ syntax to declare temporary values then you’re free of this restriction.
e.g.
function areaOf5() =
let(
pi =3.142,
)
object(
r = 5,
area = pi * pow(r,2)
)
In this example the definition of pi is ‘lost’ as a member of the object when the function is called, but you can include it by declaring it’s “let” value in the object definition.
e.g.
function areaOf5() =
let(
pi =3.142,
)
object(
pi = pi
r = 5,
area = pi * pow(r,2)
)
You can even do fancy stuff like:
function areaOf5() =
let(
const = object(
pi =3.142,
ftPerMile = 5280,
cToF = 5/9
miles = 7,
totalFeet = miles * ftPerMile,
r = 5,
),
c = const
)
object(
const,
area = c.pi * pow(c.r,2)
area = pi * pow(r,2)
)
So to my unsophisticated thinking; an object definition is semantically quite similar to a function, and syntactically even more similar, how much of an effort would it be to add “let()” functionality to the new object function and allow for some very valuable efficiencies in declaring objects that need to enumerate values that may share references to the same values. YES, there is a workaround, but the coding with those workarounds can get increasingly obscure. I think anyone who has used the new object() function in any more than checking out out briefly may well share the frustration I’ve been encountering.
If added, then an object function could be formed like this:
areaOf5 = object(
let(
pi =3.142
)
r = 5,
area = pi * pow(r,2)
);
Thoughts and comments appreciated… especially from whomever did the fantastic job of developing and including this object function to begin with!
Thanks
Hugo
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
--
This email has been checked for viruses by AVG antivirus software.
www.avg.com
On 10/24/2025 1:20 AM, Hugo Jackson via Discuss wrote:
So to my unsophisticated thinking; an object definition is semantically quite similar to a function, and syntactically even more similar,
object() is a function. Syntactically it has no magic, and
semantically it has only a small amount of magic (in that it processes
arbitrary names and an arbitrary number of arguments).
As a function, its semantics don't really allow for what you suggest.
For instance, you can't say
cylinder(h = 10, d = h);
(Which is a module, but the same concepts apply.)
how much of an effort would it be to add “let()” functionality to the new object function
let(), on the other hand, is magic. It looks sort of dimly like a
function, but isn't a function at all.
Could object() be made magic, like let()? Yes. In fact, one of the
variations proposed was a variation on let(). (I disliked it because it
made let() be even more magic.)
Would I want it to be made more magic? No. Magic is bad. Magic
makes things hard to understand. Why would this work with object() and
not with cylinder()? No good reason, no understandable reason, just
that object() is magic.
Why not make it work with arbitrary function/module calls, so that
that cylinder() example would work? Might be interesting, but would
almost certainly break existing models.
That was always one of the appeals of object(), that it could create
objects without requiring magic.
So are you just stuck with putting a let() in front of your object()?
Well, first, note that you would often want that anyway, as you show in
your later examples. You probably don't really want to include pi in
your object, because pi is a constant and wouldn't add any value.
(Yes, I know it was just an example; I'm turning it into a different
example.) The values that you want to return depend on pi, but pi itself
isn't interesting to the caller; it's an intermediate value. You would
want to set it with a let(), because you wouldn't want to include it
in the object.
The kinds of things where your desire would really apply would be where
you want your object to have both A and B, and B's value depends on A.
Brief side trip, that doesn't help you:
I've always thought of object() as a stepping stone, a syntactically and
semantically "safe" mechanism that would let us create objects in a
variety of creative ways, but that would for most common purposes be
supplanted by a native object-literal syntax. That object-literal
syntax is proposed
https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)
to be similar to JavaScript object literals
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
and Python dictionary literals
https://docs.python.org/3/tutorial/datastructures.html#dictionaries:
o = { a: 123, b: "hello", c: [1,2,3] };
But that doesn't help you, because a, b, and c there are not variables;
they are member names and they aren't accessible until the object is "done".
(And note: if you want to build a list where the second value is based
on the first, there's no way to say that. Object literals are sort of
like list literals in that way.)
Back from the side trip...
My original concept for object literals would have helped, because it
really did use variables:
o = { a = 1; b = "hello"; c = [1,2,3]; };
where the stuff between the braces was arbitrary OpenSCAD statements and
those are ordinary assignments. I liked that scheme, because it also
allowed for something else I wanted to do (see the full OEP8
https://github.com/openscad/openscad/wiki/OEP8:-Objects-(dictionaries%3F),-Geometry-as-data,-and-Module-References):
shape values (called in that proposal "geometry values").
o = { size = 10; cube(size); };
"o" would then contain both "size", so that you could say "o.size", and
would contain a cube that you could insert into your model as desired.
(And since it's all just a value, you could put such a construct in an
array, pass it around as a parameter, return it from a function, et
cetera.) I called it the "hybrid" scheme because it combined "data"
objects with shapes.
That scheme had one big problem: the shape value was pretty magic.
Having an object that didn't happen to include a shape wasn't really a
problem, but what if you wanted to extract only the shape? The shape
didn't have a name. Or what if you wanted the values, but not the
shape? There are ways, but none of them seemed really clean. Worse,
what if you wanted two shapes in your object? There's only one place
for the shape; a second shape would have to be a nested object. Also,
there was a desire to support some sort of "object comprehension" akin
to list comprehension, and it wasn't at all obvious how that would fit
into the "ordinary OpenSCAD syntax" scheme.
The alternative was to have a separate syntax for shape literals:
s = {{ size = 10; cube(size); }};
where again the stuff between the braces is arbitrary OpenSCAD
statements, but the variables are not turned into any sort of object
members; the only thing coming out of the expression is the shape.
I flipped the coin a lot. If you look at the history of PR#4478, there
was a time when it supported both syntaxes, using IIRC {{( ... )}} as a
temporary syntax for the hybrid scheme. Eventually, because of the "two
shapes" problem and the "object comprehension" problem with the hybrid
scheme, and because the syntax was similar to the JavaScript and Python
syntaxes, I came down on the side of the separate object and shape
syntaxes that you see now in OEP8.
Note that even with the "hybrid" scheme you would still sometimes want
to wrap it in a let(), because you wouldn't want to be required to
include intermediate values in your resulting object - and if you have
values A and B that you want to include, and intermediate value X is
dependent on A, and B is dependent on X, you're back to having to put A
and X into the let().
o = {
A = 1;
X = A * 3; // intermediate value
B = X + 4;
};
would leave you with o containing o.X, which would be undesirable.
You'd have to do
o = let(
A = 1,
X = A * 3; // intermediate value
) {
A = A;
B = X + 4;
};
The current generation of PR#4478 / OEP8 no longer includes the "hybrid"
scheme, but it could be resurrected. But if it was resurrected, we'd be
back to the "two shapes" and "object comprehension" problems.
Going back for a moment to one of your examples:
You can even do fancy stuff like:
function areaOf5() =
let(
const = object(
pi =3.142,
ftPerMile = 5280,
cToF = 5/9
miles = 7,
totalFeet = miles * ftPerMile,
r = 5,
),
c = const
)
object(
const,
area = c.pi * pow(c.r,2)
area = pi * pow(r,2)
)
That will work fine today. You don't need separate variables for
"const" and "c".
Anyhow, net, I don't have a good answer for you. Trying to keep the
syntax and semantics as simple and non-magic as possible, and familiar
to people coming from other languages, led to designs that didn't
support the idea that you could refer to a previous value.
Creating a temporary object and then using it as the basis for your
final object is a decent pattern, at least for the simple cases. It
would be good for cases where you have a few members that are
"primitive" and a few more that are derived from those first few. It
would not scale well to a case where you have a desired result A, an
intermediate X that depends on A, a desired result B that depends on X,
an intermediate Y that depends on B, a desired result C that depends on
Y, et cetera.
Thoughts and comments appreciated… especially from whomever did the fantastic job of developing and including this object function to begin with!
It was a group effort. The original concepts go back into OpenSCAD
antiquity, in PR#3087, issue #3088, and perhaps others. I did the
initial internal implementation in support of textmetrics(). Revar
Desmera did the original implementation of the object() function. I
incorporated that into PR#4478, along with the object-literal syntaxes
discussed above. Peter Kriens extracted object() and pushed it over the
finish line.
I am so grateful to all of you who are doing the thoughtful heavy
lifting on this topic.
Thank you
Jon
On 10/24/2025 3:00 PM, Jordan Brown via Discuss wrote:
On 10/24/2025 1:20 AM, Hugo Jackson via Discuss wrote:
So to my unsophisticated thinking; an object definition is semantically quite similar to a function, and syntactically even more similar,
object() is a function. Syntactically it has no magic, and
semantically it has only a small amount of magic (in that it processes
arbitrary names and an arbitrary number of arguments).
As a function, its semantics don't really allow for what you suggest.
For instance, you can't say
cylinder(h = 10, d = h);
(Which is a module, but the same concepts apply.)
how much of an effort would it be to add “let()” functionality to the new object function
let(), on the other hand, is magic. It looks sort of dimly like a
function, but isn't a function at all.
Could object() be made magic, like let()? Yes. In fact, one of the
variations proposed was a variation on let(). (I disliked it because
it made let() be even more magic.)
Would I want it to be made more magic? No. Magic is bad. Magic
makes things hard to understand. Why would this work with object()
and not with cylinder()? No good reason, no understandable reason,
just that object() is magic.
Why not make it work with arbitrary function/module calls, so that
that cylinder() example would work? Might be interesting, but
would almost certainly break existing models.
That was always one of the appeals of object(), that it could create
objects without requiring magic.
So are you just stuck with putting a let() in front of your object()?
Well, first, note that you would often want that anyway, as you show
in your later examples. You probably don't really want to include pi
in your object, because pi is a constant and wouldn't add any value.
(Yes, I know it was just an example; I'm turning it into a different
example.) The values that you want to return depend on pi, but pi
itself isn't interesting to the caller; it's an intermediate value.
You would want to set it with a let(), because you wouldn't want to
include it in the object.
The kinds of things where your desire would really apply would be
where you want your object to have both A and B, and B's value depends
on A.
Brief side trip, that doesn't help you:
I've always thought of object() as a stepping stone, a syntactically
and semantically "safe" mechanism that would let us create objects in
a variety of creative ways, but that would for most common purposes be
supplanted by a native object-literal syntax. That object-literal
syntax is proposed
https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_openscad_openscad_wiki_OEP8a-3A-2D-2DObjects-2D-28dictionaries-253F-29&d=DwMFaQ&c=euGZstcaTDllvimEN8b7jXrwqOf-v5A_CdpgnVfiiMM&r=AsrE-c7ZR7B2Kyr3qgfvvppkCEBVsNmwEMndcrRSuOI&m=x9ST01LqtYnb7QX9JlSvoobIjhKhOgK2yUiatSw02SZ9CmOaby-GjhUnxYS3511O&s=l_Mn4lOnccjWiWFDdvqs-B4IpeDHs1a2PBC2fecTi2c&e=
to be similar to JavaScript object literals
https://urldefense.proofpoint.com/v2/url?u=https-3A__developer.mozilla.org_en-2DUS_docs_Web_JavaScript_Reference_Operators_Object-5Finitializer&d=DwMFaQ&c=euGZstcaTDllvimEN8b7jXrwqOf-v5A_CdpgnVfiiMM&r=AsrE-c7ZR7B2Kyr3qgfvvppkCEBVsNmwEMndcrRSuOI&m=x9ST01LqtYnb7QX9JlSvoobIjhKhOgK2yUiatSw02SZ9CmOaby-GjhUnxYS3511O&s=IMm-RB2nXR8VpNy_Tn4GjyfyFRsmfLJVx4DW-e6Q954&e=
and Python dictionary literals
https://urldefense.proofpoint.com/v2/url?u=https-3A__docs.python.org_3_tutorial_datastructures.html-23dictionaries&d=DwMFaQ&c=euGZstcaTDllvimEN8b7jXrwqOf-v5A_CdpgnVfiiMM&r=AsrE-c7ZR7B2Kyr3qgfvvppkCEBVsNmwEMndcrRSuOI&m=x9ST01LqtYnb7QX9JlSvoobIjhKhOgK2yUiatSw02SZ9CmOaby-GjhUnxYS3511O&s=R3vHFhoD95_8J5OX2MWIPHXePc-sQA89ScmL0nd2WZA&e=:
o = { a: 123, b: "hello", c: [1,2,3] };
But that doesn't help you, because a, b, and c there are not
variables; they are member names and they aren't accessible until the
object is "done".
(And note: if you want to build a list where the second value is
based on the first, there's no way to say that. Object literals are
sort of like list literals in that way.)
Back from the side trip...
My original concept for object literals would have helped, because
it really did use variables:
o = { a = 1; b = "hello"; c = [1,2,3]; };
where the stuff between the braces was arbitrary OpenSCAD statements
and those are ordinary assignments. I liked that scheme, because it
also allowed for something else I wanted to do (see the full OEP8
https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_openscad_openscad_wiki_OEP8-3A-2DObjects-2D-28dictionaries-253F-29-2C-2DGeometry-2Das-2Ddata-2C-2Dand-2DModule-2DReferences&d=DwMFaQ&c=euGZstcaTDllvimEN8b7jXrwqOf-v5A_CdpgnVfiiMM&r=AsrE-c7ZR7B2Kyr3qgfvvppkCEBVsNmwEMndcrRSuOI&m=x9ST01LqtYnb7QX9JlSvoobIjhKhOgK2yUiatSw02SZ9CmOaby-GjhUnxYS3511O&s=gTlFkdx0uO8MMdqdE4gc_M1FP1b3n6POSn9yWtlH4JY&e=):
shape values (called in that proposal "geometry values").
o = { size = 10; cube(size); };
"o" would then contain both "size", so that you could say "o.size",
and would contain a cube that you could insert into your model as
desired. (And since it's all just a value, you could put such a
construct in an array, pass it around as a parameter, return it from a
function, et cetera.) I called it the "hybrid" scheme because it
combined "data" objects with shapes.
That scheme had one big problem: the shape value was pretty magic.
Having an object that didn't happen to include a shape wasn't really a
problem, but what if you wanted to extract only the shape? The
shape didn't have a name. Or what if you wanted the values, but not
the shape? There are ways, but none of them seemed really clean.
Worse, what if you wanted two shapes in your object? There's only
one place for the shape; a second shape would have to be a nested
object. Also, there was a desire to support some sort of "object
comprehension" akin to list comprehension, and it wasn't at all
obvious how that would fit into the "ordinary OpenSCAD syntax" scheme.
The alternative was to have a separate syntax for shape literals:
s = {{ size = 10; cube(size); }};
where again the stuff between the braces is arbitrary OpenSCAD
statements, but the variables are not turned into any sort of object
members; the only thing coming out of the expression is the shape.
I flipped the coin a lot. If you look at the history of PR#4478,
there was a time when it supported both syntaxes, using IIRC {{( ...
)}} as a temporary syntax for the hybrid scheme. Eventually, because
of the "two shapes" problem and the "object comprehension" problem
with the hybrid scheme, and because the syntax was similar to the
JavaScript and Python syntaxes, I came down on the side of the
separate object and shape syntaxes that you see now in OEP8.
Note that even with the "hybrid" scheme you would still sometimes want
to wrap it in a let(), because you wouldn't want to be required to
include intermediate values in your resulting object - and if you have
values A and B that you want to include, and intermediate value X is
dependent on A, and B is dependent on X, you're back to having to put
A and X into the let().
o = {
A = 1;
X = A * 3; // intermediate value
B = X + 4;
};
would leave you with o containing o.X, which would be undesirable.
You'd have to do
o = let(
A = 1,
X = A * 3; // intermediate value
) {
A = A;
B = X + 4;
};
The current generation of PR#4478 / OEP8 no longer includes the
"hybrid" scheme, but it could be resurrected. But if it was
resurrected, we'd be back to the "two shapes" and "object
comprehension" problems.
Going back for a moment to one of your examples:
You can even do fancy stuff like:
function areaOf5() =
let(
const = object(
pi =3.142,
ftPerMile = 5280,
cToF = 5/9
miles = 7,
totalFeet = miles * ftPerMile,
r = 5,
),
c = const
)
object(
const,
area = c.pi * pow(c.r,2)
area = pi * pow(r,2)
)
That will work fine today. You don't need separate variables for
"const" and "c".
Anyhow, net, I don't have a good answer for you. Trying to keep the
syntax and semantics as simple and non-magic as possible, and familiar
to people coming from other languages, led to designs that didn't
support the idea that you could refer to a previous value.
Creating a temporary object and then using it as the basis for your
final object is a decent pattern, at least for the simple cases. It
would be good for cases where you have a few members that are
"primitive" and a few more that are derived from those first few. It
would not scale well to a case where you have a desired result A, an
intermediate X that depends on A, a desired result B that depends on
X, an intermediate Y that depends on B, a desired result C that
depends on Y, et cetera.
Thoughts and comments appreciated… especially from whomever did the fantastic job of developing and including this object function to begin with!
It was a group effort. The original concepts go back into OpenSCAD
antiquity, in PR#3087, issue #3088, and perhaps others. I did the
initial internal implementation in support of textmetrics(). Revar
Desmera did the original implementation of the object() function. I
incorporated that into PR#4478, along with the object-literal syntaxes
discussed above. Peter Kriens extracted object() and pushed it over
the finish line.
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
--
This email has been checked for viruses by AVG antivirus software.
www.avg.com
On 10/24/2025 2:59 PM, Jon Bondy wrote:
I am so grateful to all of you who are doing the thoughtful heavy
lifting on this topic.
You're welcome. (I should note, though, that while I'm pretty vocal I
am in the grand scheme of things a minor contributor.)
I know that it is frustrating when you say "couldn't you just XXX",
where XXX seems simple, and you get told "no".
The problem, often, is that while XXX would in fact be simple to do, it
wouldn't quite fit in; it would be a special little wart. One special
little wart is no big deal, but then next week somebody asks for YYY,
and the week after for ZZZ, and pretty soon half your manual is listing
all of the weird little special cases, and when you go to add something
more general it collides with one of them - and once you added that
wart, somebody will scream if it gets taken away.
There are additional problems with ideas that are close variations on
other things. If you create a creature that looks like a duck, and
walks like a duck, and oinks like a pig, an observer who sees something
that looks like a duck will never be quite sure what it is that they are
seeing. Something that looks like a function but doesn't quite act
like a function makes people have trouble coming up with simple patterns
that explain behavior.
I would expect that the natural way to create objects would be as returns
from a function. In that case you’ll have a let already to do function’s
work and the existing behavior won’t seem like a problem. You will compute
all the object’s fields in the let anyway.
On Fri, Oct 24, 2025 at 15:01 Jordan Brown via Discuss <
discuss@lists.openscad.org> wrote:
On 10/24/2025 1:20 AM, Hugo Jackson via Discuss wrote:
So to my unsophisticated thinking; an object definition is semantically quite similar to a function, and syntactically even more similar,
object() is a function. Syntactically it has no magic, and semantically
it has only a small amount of magic (in that it processes arbitrary names
and an arbitrary number of arguments).
As a function, its semantics don't really allow for what you suggest. For
instance, you can't say
cylinder(h = 10, d = h);
(Which is a module, but the same concepts apply.)
how much of an effort would it be to add “let()” functionality to the new object function
let(), on the other hand, is magic. It looks sort of dimly like a
function, but isn't a function at all.
Could object() be made magic, like let()? Yes. In fact, one of the
variations proposed was a variation on let(). (I disliked it because it
made let() be even more magic.)
Would I want it to be made more magic? No. Magic is bad. Magic makes
things hard to understand. Why would this work with object() and not with
cylinder()? No good reason, no understandable reason, just that object()
is magic.
Why not make it work with arbitrary function/module calls, so that that
cylinder() example would work? Might be interesting, but would almost
certainly break existing models.
That was always one of the appeals of object(), that it could create
objects without requiring magic.
So are you just stuck with putting a let() in front of your object()?
Well, first, note that you would often want that anyway, as you show in
your later examples. You probably don't really want to include pi in your
object, because pi is a constant and wouldn't add any value. (Yes, I know
it was just an example; I'm turning it into a different example.) The
values that you want to return depend on pi, but pi itself isn't
interesting to the caller; it's an intermediate value. You would want to
set it with a let(), because you wouldn't want to include it in the object.
The kinds of things where your desire would really apply would be where
you want your object to have both A and B, and B's value depends on A.
Brief side trip, that doesn't help you:
I've always thought of object() as a stepping stone, a syntactically and
semantically "safe" mechanism that would let us create objects in a variety
of creative ways, but that would for most common purposes be supplanted by
a native object-literal syntax. That object-literal syntax is proposed
https://github.com/openscad/openscad/wiki/OEP8a:--Objects-(dictionaries%3F)
to be similar to JavaScript object literals
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
and Python dictionary literals
https://docs.python.org/3/tutorial/datastructures.html#dictionaries:
o = { a: 123, b: "hello", c: [1,2,3] };
But that doesn't help you, because a, b, and c there are not variables;
they are member names and they aren't accessible until the object is "done".
(And note: if you want to build a list where the second value is based on
the first, there's no way to say that. Object literals are sort of like
list literals in that way.)
Back from the side trip...
My original concept for object literals would have helped, because it
really did use variables:
o = { a = 1; b = "hello"; c = [1,2,3]; };
where the stuff between the braces was arbitrary OpenSCAD statements and
those are ordinary assignments. I liked that scheme, because it also
allowed for something else I wanted to do (see the full OEP8
https://github.com/openscad/openscad/wiki/OEP8:-Objects-(dictionaries%3F),-Geometry-as-data,-and-Module-References):
shape values (called in that proposal "geometry values").
o = { size = 10; cube(size); };
"o" would then contain both "size", so that you could say "o.size", and
would contain a cube that you could insert into your model as desired.
(And since it's all just a value, you could put such a construct in an
array, pass it around as a parameter, return it from a function, et
cetera.) I called it the "hybrid" scheme because it combined "data"
objects with shapes.
That scheme had one big problem: the shape value was pretty magic.
Having an object that didn't happen to include a shape wasn't really a
problem, but what if you wanted to extract only the shape? The shape
didn't have a name. Or what if you wanted the values, but not the shape?
There are ways, but none of them seemed really clean. Worse, what if you
wanted two shapes in your object? There's only one place for the
shape; a second shape would have to be a nested object. Also, there was a
desire to support some sort of "object comprehension" akin to list
comprehension, and it wasn't at all obvious how that would fit into the
"ordinary OpenSCAD syntax" scheme.
The alternative was to have a separate syntax for shape literals:
s = {{ size = 10; cube(size); }};
where again the stuff between the braces is arbitrary OpenSCAD statements,
but the variables are not turned into any sort of object members; the
only thing coming out of the expression is the shape.
I flipped the coin a lot. If you look at the history of PR#4478, there
was a time when it supported both syntaxes, using IIRC {{( ... )}} as a
temporary syntax for the hybrid scheme. Eventually, because of the "two
shapes" problem and the "object comprehension" problem with the hybrid
scheme, and because the syntax was similar to the JavaScript and Python
syntaxes, I came down on the side of the separate object and shape syntaxes
that you see now in OEP8.
Note that even with the "hybrid" scheme you would still sometimes want to
wrap it in a let(), because you wouldn't want to be required to include
intermediate values in your resulting object - and if you have values A and
B that you want to include, and intermediate value X is dependent on A, and
B is dependent on X, you're back to having to put A and X into the let().
o = {
A = 1;
X = A * 3; // intermediate value
B = X + 4;
};
would leave you with o containing o.X, which would be undesirable. You'd
have to do
o = let(
A = 1,
X = A * 3; // intermediate value
) {
A = A;
B = X + 4;
};
The current generation of PR#4478 / OEP8 no longer includes the "hybrid"
scheme, but it could be resurrected. But if it was resurrected, we'd be
back to the "two shapes" and "object comprehension" problems.
Going back for a moment to one of your examples:
You can even do fancy stuff like:
function areaOf5() =
let(
const = object(
pi =3.142,
ftPerMile = 5280,
cToF = 5/9
miles = 7,
totalFeet = miles * ftPerMile,
r = 5,
),
c = const
)
object(
const,
area = c.pi * pow(c.r,2)
area = pi * pow(r,2)
)
That will work fine today. You don't need separate variables for "const"
and "c".
Anyhow, net, I don't have a good answer for you. Trying to keep the
syntax and semantics as simple and non-magic as possible, and familiar to
people coming from other languages, led to designs that didn't support the
idea that you could refer to a previous value.
Creating a temporary object and then using it as the basis for your final
object is a decent pattern, at least for the simple cases. It would be
good for cases where you have a few members that are "primitive" and a few
more that are derived from those first few. It would not scale well to a
case where you have a desired result A, an intermediate X that depends on
A, a desired result B that depends on X, an intermediate Y that depends on
B, a desired result C that depends on Y, et cetera.
Thoughts and comments appreciated… especially from whomever did the fantastic job of developing and including this object function to begin with!
It was a group effort. The original concepts go back into OpenSCAD
antiquity, in PR#3087, issue #3088, and perhaps others. I did the initial
internal implementation in support of textmetrics(). Revar Desmera did the
original implementation of the object() function. I incorporated that into
PR#4478, along with the object-literal syntaxes discussed above. Peter
Kriens extracted object() and pushed it over the finish line.
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org