discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Re: New feature in 2025.07.11: the object() function

RD
Revar Desmera
Mon, Aug 25, 2025 1:07 AM

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),
...
)

  • Revar
> 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), ... ) - Revar
JB
Jordan Brown
Mon, Aug 25, 2025 7:58 AM

On 8/25/2025 2:23 AM, Cory Cross wrote:

Should we rename the data type, and preserve that different name forever?

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:

  • Create object:  { name: val, ... }
  • Access member with constant name:  obj.name
  • Access member with variable name:  obj[expr]

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:

  • Do something sort of like a mainstream language.
  • Copy some obscure language that does have immutable values.
  • Do something entirely novel.

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/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: * Create object:  { name: val, ... } * Access member with constant name:  obj.name * Access member with variable name:  obj[expr] 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: * Do something sort of like a mainstream language. * Copy some obscure language that *does* have immutable values. * Do something entirely novel. 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.
CC
Cory Cross
Mon, Aug 25, 2025 2:56 PM

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.

  • Cory
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.html>Elixir 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>. - Cory
HJ
Hugo Jackson
Fri, Oct 24, 2025 8:20 AM

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

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
JB
Jon Bondy
Fri, Oct 24, 2025 10:48 AM

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

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
JB
Jordan Brown
Fri, Oct 24, 2025 7:00 PM

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.

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.
JB
Jon Bondy
Fri, Oct 24, 2025 9:59 PM

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

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
JB
Jordan Brown
Fri, Oct 24, 2025 10:20 PM

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.

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.
AM
Adrian Mariano
Fri, Oct 24, 2025 11:34 PM

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

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