discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

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

CC
Cory Cross
Fri, Aug 15, 2025 3:07 AM

Also as someone else noted the task of rewriting to use the new

feature is

a big one and not backwards compatible (except in the case of changes

that

are entirely internal).

I'd like to pick out an example: VNFs.

"(VNF) holds the data used by polyhedron() to construct objects: a
vertex list and a list of faces."

So currently it's a 2-element list, we have functions:

function vnf_vertices(vnf) = vnf[0];
function vnf_faces(vnf) = vnf[1];

What if numerical indices of an object were instead the fields of an
object in the order they're given? Then you could change the following
line (which is creating a vnf right now)

[face, [faceidxs]]

with

new_vnf(face,faceidxs)

and all user code would continue working (provided new_vnf() calls
object appropriately) in between that's referencing by index.

Numbers aren't valid identifiers anyway, so you aren't losing much
besides having keys be numerical.

Then, all receiver functions can have a cast_to_vnf(maybe_vnf) =
is_object(maybe_vnf,... what defines vnf class?...) || assert(...)
new_vnf(maybe_vnf[0],maybe_vnf[1]);

Then in some years it can be ripped out or use an option flags to skip
check efficiently?

====================

An object has each of its members copied into the new object.

I assume this is done for efficiency and implementation reasons, but
this precludes having OO inheritance and makes it much more of a
record than an object.

====================

Methods: If a function reference comes from an object or a vector, it
should see a special variable $this that refers to the containing object
or vector.

All other "special" $-variables are dynamically scoped. I do not think
the dollar sign should be used, as this is statically scoped. These
methods would all be new, so having the an implicit local variable
this should be backward-compatible (just like I can override circle
today).

> Also as someone else noted the task of rewriting to use the new feature is > a big one and not backwards compatible (except in the case of changes that > are entirely internal). I'd like to pick out an example: VNFs. "(VNF) holds the data used by polyhedron() to construct objects: a vertex list and a list of faces." So currently it's a 2-element list, we have functions: function vnf_vertices(vnf) = vnf[0]; function vnf_faces(vnf) = vnf[1]; What if numerical indices of an object were instead the fields of an object in the order they're given? Then you could change the following line (which is creating a vnf right now) [face, [faceidxs]] with new_vnf(face,faceidxs) and all user code would continue working (provided new_vnf() calls object appropriately) in between that's referencing by index. Numbers aren't valid identifiers anyway, so you aren't losing much besides having keys be numerical. Then, all receiver functions can have a cast_to_vnf(maybe_vnf) = is_object(maybe_vnf,... what defines vnf class?...) || assert(...) new_vnf(maybe_vnf[0],maybe_vnf[1]); Then in some years it can be ripped out or use an option flags to skip check efficiently? ==================== > An object has each of its members copied into the new object. I assume this is done for efficiency and implementation reasons, but this precludes having OO inheritance and makes it much more of a `record` than an `object`. ==================== >Methods: If a function reference comes from an object or a vector, it > should see a special variable $this that refers to the containing object > or vector. All other "special" $-variables are dynamically scoped. I do not think the dollar sign should be used, as this is statically scoped. These methods would all be new, so having the an implicit local variable `this` should be backward-compatible (just like I can override `circle` today).
JB
Jordan Brown
Fri, Aug 15, 2025 6:13 AM

On 8/15/2025 5:07 AM, Cory Cross via Discuss wrote:

What if numerical indices of an object were instead the fields of an
object in the order they're given?

I'm not really comfortable with that, but I can't really articulate
why.  I think of objects as unordered; I made them have some order only
because I didn't like the presentation when echo() displayed them in
random order.  But once it has an order, I can't articulate why we
shouldn't document and use that order.

Numbers aren't valid identifiers anyway, so you aren't losing much
besides having keys be numerical.

I'd like objects to allow non-string keys in the future.

An object has each of its members copied into the new object.

I assume this is done for efficiency and implementation reasons, but
this precludes having OO inheritance and makes it much more of a
record than an object.

As they are immutable, you can't tell the difference.

There's more than one style of OO inheritance.  What we've got here is
more like the JavaScript "prototype object" style of inheritance:  you
can take an object that you like, and make a modified copy of it.

Methods: If a function reference comes from an object or a vector, it
should see a special variable $this that refers to the containing

object

or vector.

All other "special" $-variables are dynamically scoped. I do not think
the dollar sign should be used, as this is statically scoped. These
methods would all be new, so having the an implicit local variable
this should be backward-compatible (just like I can override
circle today).

Check out the discussion in PR#6022.  Neither has exactly the right
scope, and there are some tricky cases.

On 8/15/2025 5:07 AM, Cory Cross via Discuss wrote: > What if numerical indices of an object were instead the fields of an > object in the order they're given? I'm not really comfortable with that, but I can't really articulate why.  I think of objects as unordered; I made them have some order only because I didn't like the presentation when echo() displayed them in random order.  But once it has an order, I can't articulate why we shouldn't document and use that order. > Numbers aren't valid identifiers anyway, so you aren't losing much > besides having keys be numerical. I'd like objects to allow non-string keys in the future. > > An object has each of its members copied into the new object. > > I assume this is done for efficiency and implementation reasons, but > this precludes having OO inheritance and makes it much more of a > `record` than an `object`. As they are immutable, you can't tell the difference. There's more than one style of OO inheritance.  What we've got here is more like the JavaScript "prototype object" style of inheritance:  you can take an object that you like, and make a modified copy of it. > >Methods: If a function reference comes from an object or a vector, it > > should see a special variable $this that refers to the containing > object > > or vector. > > All other "special" $-variables are dynamically scoped. I do not think > the dollar sign should be used, as this is statically scoped. These > methods would all be new, so having the an implicit local variable > `this` should be backward-compatible (just like I can override > `circle` today). Check out the discussion in PR#6022.  Neither has exactly the right scope, and there are some tricky cases.
CC
Cory Cross
Fri, Aug 15, 2025 8:22 AM

On 8/14/25 11:13 PM, Jordan Brown via Discuss wrote:

On 8/15/2025 5:07 AM, Cory Cross via Discuss wrote:

Numbers aren't valid identifiers anyway, so you aren't losing much
besides having keys be numerical.

I'd like objects to allow non-string keys in the future.

Hmm, I think it'd actually be simple to use as needed:

function wrap_keys(obj) =
  let ( keys = list(for(k=obj) k),
         key_vec = list(for(i=0:len(keys)-1) { [i, obj[keys[i]]] })
       ) object(key_vec,obj);

... or something close to that.

An object has each of its members copied into the new object.

I assume this is done for efficiency and implementation reasons, but
this precludes having OO inheritance and makes it much more of a
record than an object.

As they are immutable, you can't tell the difference.

There's more than one style of OO inheritance.  What we've got here is
more like the JavaScript "prototype object" style of inheritance:  you
can take an object that you like, and make a modified copy of it.

The difference is you can't have super, which ECMAScript does have.

On 8/14/25 11:13 PM, Jordan Brown via Discuss wrote: > On 8/15/2025 5:07 AM, Cory Cross via Discuss wrote: >> Numbers aren't valid identifiers anyway, so you aren't losing much >> besides having keys be numerical. > > I'd like objects to allow non-string keys in the future. Hmm, I think it'd actually be simple to use as needed: function wrap_keys(obj) =   let ( keys = list(for(k=obj) k),          key_vec = list(for(i=0:len(keys)-1) { [i, obj[keys[i]]] })        ) object(key_vec,obj); ... or something close to that. >> > An object has each of its members copied into the new object. >> >> I assume this is done for efficiency and implementation reasons, but >> this precludes having OO inheritance and makes it much more of a >> `record` than an `object`. > > > As they are immutable, you can't tell the difference. > > There's more than one style of OO inheritance.  What we've got here is > more like the JavaScript "prototype object" style of inheritance:  you > can take an object that you like, and make a modified copy of it. The difference is you can't have super, which ECMAScript does have.
PK
Peter Kriens
Fri, Aug 15, 2025 8:45 AM

On 15 Aug 2025, at 08:13, Jordan Brown via Discuss discuss@lists.openscad.org wrote:
On 8/15/2025 5:07 AM, Cory Cross via Discuss wrote:

What if numerical indices of an object were instead the fields of an object in the order they're given?

I'm not really comfortable with that, but I can't really articulate why.  I think of objects as unordered; I made them have some order only because I didn't like the presentation when echo() displayed them in random order.  But once it has an order, I can't articulate why we shouldn't document and use that order.

In my experience an extremely well defined order is paramount. Without a defined order we could not even run our tests since it checks the output of echo, which uses that order.

I ran an open source project for building Java/OSGi tools. We learned the hard way that the random ordering of hash maps cause many problems, primary in repeatable builds.

I think it makes perfect sense to support object[n] when n is an integer it uses it as an index.

Numbers aren't valid identifiers anyway, so you aren't losing much besides having keys be numerical.

I'd like objects to allow non-string keys in the future.

I think you want way too much here :-) That would be a map and that is in my mind a very different beast then an object.

Our 'object' is what a struct or record is in most languages. It has field names that are known to the code, it is a type. In a map you have a mapping from one value to another, which is fundamentally different. A map requires customization for the key types because for performance you need to define a search algorithm like hashing or ordered so you likely have several different map types. You probably also will need counting maps, ordered maps, sorted maps, etc. I fail to see why this should be a standard feature of core OpenSCAD like object(), this is user space stuff.

The object() feature wil make this more straightforward to implement but we should imho not confuse object() with a general map. It is basically a record/struct and there is imho no reason not to index it by an integer. Especially if this makes porting easier.

An object has each of its members copied into the new object.

I assume this is done for efficiency and implementation reasons, but this precludes having OO inheritance and makes it much more of a record than an object.

As they are immutable, you can't tell the difference.

There's more than one style of OO inheritance.  What we've got here is more like the JavaScript "prototype object" style of inheritance:  you can take an object that you like, and make a modified copy of it.

Methods: If a function reference comes from an object or a vector, it
should see a special variable $this that refers to the containing object
or vector.

All other "special" $-variables are dynamically scoped. I do not think the dollar sign should be used, as this is statically scoped. These methods would all be new, so having the an implicit local variable this should be backward-compatible (just like I can override circle today).

Check out the discussion in PR#6022.  Neither has exactly the right scope, and there are some tricky cases.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

> On 15 Aug 2025, at 08:13, Jordan Brown via Discuss <discuss@lists.openscad.org> wrote: > On 8/15/2025 5:07 AM, Cory Cross via Discuss wrote: >> What if numerical indices of an object were instead the fields of an object in the order they're given? > I'm not really comfortable with that, but I can't really articulate why. I think of objects as unordered; I made them have some order only because I didn't like the presentation when echo() displayed them in random order. But once it has an order, I can't articulate why we shouldn't document and use that order. > In my experience an extremely well defined order is paramount. Without a defined order we could not even run our tests since it checks the output of echo, which uses that order. I ran an open source project for building Java/OSGi tools. We learned the hard way that the random ordering of hash maps cause many problems, primary in repeatable builds. I think it makes perfect sense to support object[n] when n is an integer it uses it as an index. > >> Numbers aren't valid identifiers anyway, so you aren't losing much besides having keys be numerical. > I'd like objects to allow non-string keys in the future. > I think you want way too much here :-) That would be a map and that is in my mind a very different beast then an object. Our 'object' is what a struct or record is in most languages. It has field names that are known to the code, it is a type. In a map you have a mapping from one value to another, which is fundamentally different. A map requires customization for the key types because for performance you need to define a search algorithm like hashing or ordered so you likely have several different map types. You probably also will need counting maps, ordered maps, sorted maps, etc. I fail to see why this should be a standard feature of core OpenSCAD like `object()`, this is user space stuff. The `object()` feature wil make this more straightforward to implement but we should imho not confuse `object()` with a general map. It is basically a record/struct and there is imho no reason not to index it by an integer. Especially if this makes porting easier. >> > An object has each of its members copied into the new object. >> >> I assume this is done for efficiency and implementation reasons, but this precludes having OO inheritance and makes it much more of a `record` than an `object`. > As they are immutable, you can't tell the difference. > > There's more than one style of OO inheritance. What we've got here is more like the JavaScript "prototype object" style of inheritance: you can take an object that you like, and make a modified copy of it. > >> >Methods: If a function reference comes from an object or a vector, it >> > should see a special variable $this that refers to the containing object >> > or vector. >> >> All other "special" $-variables are dynamically scoped. I do not think the dollar sign should be used, as this is statically scoped. These methods would all be new, so having the an implicit local variable `this` should be backward-compatible (just like I can override `circle` today). > Check out the discussion in PR#6022. Neither has exactly the right scope, and there are some tricky cases. > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
JO
jjvb-openscad (at) bassklampfe.de
Fri, Aug 15, 2025 9:50 AM

Am 15.08.25 um 10:45 schrieb Peter Kriens via Discuss:

In my experience an extremely well defined order is paramount. Without
a defined order we could not even run our tests since it checks the
output of echo, which uses that order.

I ran an open source project for building Java/OSGi tools. We learned
the hard way that the random ordering of hash maps cause many
problems, primary in repeatable builds.

I was part of a big software project, which used Lua as main language.

In Lua there ist no difference between hashmap and array, both are just
tables (you can even intermix them).
And – YES - iterating over the key/value pairs using the pairs function
will give you random order. But it is very (!) performant.

And if you really need stable iteration, you can always (at the cost of
speed)

  • get all keys from the table
  • sort the keys (by any criteria you need)
  • iterate over the keys and fetch the values.

We also use this for data serialisation, so even if the key order in the
table is random, saved settings etc are always stable.

We never had issues, neither in automatic test runs nor in repeatable
builds (getting bitidentical builds by commit id is essential in
embedded software for industrial use!)

Jm2C

Am 15.08.25 um 10:45 schrieb Peter Kriens via Discuss: > In my experience an extremely well defined order is paramount. Without > a defined order we could not even run our tests since it checks the > output of echo, which uses that order. > > I ran an open source project for building Java/OSGi tools. We learned > the hard way that the random ordering of hash maps cause many > problems, primary in repeatable builds. I was part of a big software project, which used Lua as main language. In Lua there ist no difference between hashmap and array, both are just tables (you can even intermix them). And – YES - iterating over the key/value pairs using the pairs function will give you random order. But it is very (!) performant. And if you really need stable iteration, you can always (at the cost of speed) - get all keys from the table - sort the keys (by any criteria you need) - iterate over the keys and fetch the values. We also use this for data serialisation, so even if the key order in the table is random, saved settings etc are always stable. We never had issues, neither in automatic test runs nor in repeatable builds (getting bitidentical builds by commit id is essential in embedded software for industrial use!) Jm2C
PK
Peter Kriens
Fri, Aug 15, 2025 10:18 AM

On 15 Aug 2025, at 11:50, jjvbhh via Discuss discuss@lists.openscad.org wrote:

Am 15.08.25 um 10:45 schrieb Peter Kriens via Discuss:

In my experience an extremely well defined order is paramount. Without a defined order we could not even run our tests since it checks the output of echo, which uses that order.

I ran an open source project for building Java/OSGi tools. We learned the hard way that the random ordering of hash maps cause many problems, primary in repeatable builds.

I was part of a big software project, which used Lua as main language.

In Lua there ist no difference between hashmap and array, both are just tables (you can even intermix them).
And – YES - iterating over the key/value pairs using the pairs function will give you random order. But it is very (!) performant.

We currently have an implementation with a hash map[key,index] and keep the values in a vector. We only add one index operation in a vector for a [key] operation. Totally negligible. I ran a profile on some BOSL2 code ... believe me this will NEVER show up in the top 10.000 even with a 100k members.

And if you really need stable iteration, you can always (at the cost of speed)

  • get all keys from the table
  • sort the keys (by any criteria you need)
  • iterate over the keys and fetch the values.
    We also use this for data serialisation, so even if the key order in the table is random, saved settings etc are always stable.

True, but wouldn't it be nicer if they ALWAYS had a consistent ordering? Your approach sounds very error prone? Why run the risk?

We had to change our code in hundreds of places. Fortunately, Java has the LinkedHashMap that provides ordering and is API compatible with HashMap but it was a huge pain in the ass because it turned out a lot of low level code could introduce random orderings that would only show up much later in the results.

BTW, Sorting would imho be wrong for this purpose. We looked at sorting because then we could use binary search. We decided on insertion order because then then the user can order as they please.  Only dilemma was when you copy an object and find the name already exists. In this case the first one wins and reuse its position.

We never had issues, neither in automatic test runs nor in repeatable builds (getting bitidentical builds by commit id is essential in embedded software for industrial use!)

I know!

Peter Kriens

Jm2C


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

> On 15 Aug 2025, at 11:50, jjvbhh via Discuss <discuss@lists.openscad.org> wrote: > > Am 15.08.25 um 10:45 schrieb Peter Kriens via Discuss: >> In my experience an extremely well defined order is paramount. Without a defined order we could not even run our tests since it checks the output of echo, which uses that order. >> >> I ran an open source project for building Java/OSGi tools. We learned the hard way that the random ordering of hash maps cause many problems, primary in repeatable builds. > > I was part of a big software project, which used Lua as main language. > > In Lua there ist no difference between hashmap and array, both are just tables (you can even intermix them). > And – YES - iterating over the key/value pairs using the pairs function will give you random order. But it is very (!) performant. We currently have an implementation with a hash map[key,index] and keep the values in a vector. We only add one index operation in a vector for a [key] operation. Totally negligible. I ran a profile on some BOSL2 code ... believe me this will NEVER show up in the top 10.000 even with a 100k members. > And if you really need stable iteration, you can always (at the cost of speed) > - get all keys from the table > - sort the keys (by any criteria you need) > - iterate over the keys and fetch the values. > We also use this for data serialisation, so even if the key order in the table is random, saved settings etc are always stable. > True, but wouldn't it be nicer if they ALWAYS had a consistent ordering? Your approach sounds very error prone? Why run the risk? We had to change our code in hundreds of places. Fortunately, Java has the LinkedHashMap that provides ordering and is API compatible with HashMap but it was a huge pain in the ass because it turned out a lot of low level code could introduce random orderings that would only show up much later in the results. BTW, Sorting would imho be wrong for this purpose. We looked at sorting because then we could use binary search. We decided on insertion order because then then the user can order as they please. Only dilemma was when you copy an object and find the name already exists. In this case the first one wins and reuse its position. > We never had issues, neither in automatic test runs nor in repeatable builds (getting bitidentical builds by commit id is essential in embedded software for industrial use!) I know! Peter Kriens > > Jm2C > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
JB
Jordan Brown
Fri, Aug 15, 2025 5:27 PM

There's more than one style of OO inheritance.  What we've got here
is more like the JavaScript "prototype object" style of inheritance: 
you can take an object that you like, and make a modified copy of it.

The difference is you can't have super, which ECMAScript does have.

I'm afraid my ES/JS experience, though pretty extensive, is for
work-legacy reasons primarily with a 2006-ish version of JS. At that
point it didn't have a super, but you could carefully build something
that more or less worked.  Have they added a real one?

But yes, that's a concern.  See the comment I just added to PR#6022.

A key question is just how far down the rabbit hole we want to go.  Do
we want to turn OpenSCAD into a full-scale OO environment?  Can we even
do that without losing its appeal to beginners?  There's already an
avenue for that, if you're willing to accept the security issues: 
PythonSCAD.  Language design and implementation is kind of fun, but is
reinventing the wheel, usually poorly.

I have some ideas for super, but the rabbit hole keeps getting deeper...

>> There's more than one style of OO inheritance.  What we've got here >> is more like the JavaScript "prototype object" style of inheritance:  >> you can take an object that you like, and make a modified copy of it. > The difference is you can't have super, which ECMAScript does have. I'm afraid my ES/JS experience, though pretty extensive, is for work-legacy reasons primarily with a 2006-ish version of JS. At that point it didn't have a `super`, but you could carefully build something that more or less worked.  Have they added a real one? But yes, that's a concern.  See the comment I just added to PR#6022. A key question is just how far down the rabbit hole we want to go.  Do we want to turn OpenSCAD into a full-scale OO environment?  Can we even do that without losing its appeal to beginners?  There's already an avenue for that, if you're willing to accept the security issues:  PythonSCAD.  Language design and implementation is kind of fun, but is reinventing the wheel, usually poorly. I have some ideas for `super`, but the rabbit hole keeps getting deeper...
GS
Guenther Sohler
Fri, Aug 15, 2025 5:44 PM

Nope i disagree Here.
PythonSCAD is Not reinventing the wheel, its a Mix of leveraging existing
Technology, Bridging 2 worlds(Python and Scad are very interlinked) and
Quite some offering Features which openscad Refuse to have for Ages

Jordan Brown via Discuss discuss@lists.openscad.org schrieb am Fr., 15.
Aug. 2025, 19:28:

There's more than one style of OO inheritance.  What we've got here is
more like the JavaScript "prototype object" style of inheritance:  you can
take an object that you like, and make a modified copy of it.

The difference is you can't have super, which ECMAScript does have.

I'm afraid my ES/JS experience, though pretty extensive, is for
work-legacy reasons primarily with a 2006-ish version of JS. At that point
it didn't have a super, but you could carefully build something that more
or less worked.  Have they added a real one?

But yes, that's a concern.  See the comment I just added to PR#6022.

A key question is just how far down the rabbit hole we want to go.  Do we
want to turn OpenSCAD into a full-scale OO environment?  Can we even do
that without losing its appeal to beginners?  There's already an avenue for
that, if you're willing to accept the security issues:  PythonSCAD.
Language design and implementation is kind of fun, but is reinventing the
wheel, usually poorly.

I have some ideas for super, but the rabbit hole keeps getting deeper...


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Nope i disagree Here. PythonSCAD is Not reinventing the wheel, its a Mix of leveraging existing Technology, Bridging 2 worlds(Python and Scad are very interlinked) and Quite some offering Features which openscad Refuse to have for Ages Jordan Brown via Discuss <discuss@lists.openscad.org> schrieb am Fr., 15. Aug. 2025, 19:28: > There's more than one style of OO inheritance. What we've got here is > more like the JavaScript "prototype object" style of inheritance: you can > take an object that you like, and make a modified copy of it. > > The difference is you can't have super, which ECMAScript does have. > > I'm afraid my ES/JS experience, though pretty extensive, is for > work-legacy reasons primarily with a 2006-ish version of JS. At that point > it didn't have a `super`, but you could carefully build something that more > or less worked. Have they added a real one? > > But yes, that's a concern. See the comment I just added to PR#6022. > > A key question is just how far down the rabbit hole we want to go. Do we > want to turn OpenSCAD into a full-scale OO environment? Can we even do > that without losing its appeal to beginners? There's already an avenue for > that, if you're willing to accept the security issues: PythonSCAD. > Language design and implementation is kind of fun, but is reinventing the > wheel, usually poorly. > > I have some ideas for `super`, but the rabbit hole keeps getting deeper... > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
CC
Cory Cross
Fri, Aug 15, 2025 5:47 PM

On 8/15/25 10:44 AM, Guenther Sohler via Discuss wrote:

Nope i disagree Here.
PythonSCAD is Not reinventing the wheel, its a Mix of leveraging existing
Technology, Bridging 2 worlds(Python and Scad are very interlinked) and
Quite some offering Features which openscad Refuse to have for Ages

I think you misread Jordan; he's saying adding too much OO stuff to the
OpenSCAD language is reinventing the wheel, and suggests PythonSCAD is
the better approach. I think he agrees with what you wrote.

On 8/15/25 10:44 AM, Guenther Sohler via Discuss wrote: > Nope i disagree Here. > PythonSCAD is Not reinventing the wheel, its a Mix of leveraging existing > Technology, Bridging 2 worlds(Python and Scad are very interlinked) and > Quite some offering Features which openscad Refuse to have for Ages I think you misread Jordan; he's saying adding too much OO stuff to the OpenSCAD language is reinventing the wheel, and suggests PythonSCAD is the better approach. I think he agrees with what you wrote.
CC
Cory Cross
Fri, Aug 15, 2025 5:51 PM

On 8/15/25 10:27 AM, Jordan Brown via Discuss wrote:

There's more than one style of OO inheritance.  What we've got here
is more like the JavaScript "prototype object" style of
inheritance:  you can take an object that you like, and make a
modified copy of it.

The difference is you can't have super, which ECMAScript does have.

I'm afraid my ES/JS experience, though pretty extensive, is for
work-legacy reasons primarily with a 2006-ish version of JS. At that
point it didn't have a super, but you could carefully build
something that more or less worked.  Have they added a real one?

Yes.

But yes, that's a concern.  See the comment I just added to PR#6022.

I commented there too. For the sake of maximum audience, I'll copy my
questions/comments here:

You've got some goals and are trying to make /object/ work for them. I
think the goals are:

  1. Replace hardcoded [1] and [CONSTANT] index into arrays by being able
    to name values by identifier
  2. Be able to namespace functions, modules, and constants.
  3. Be able to assert some piece of data as conforming to some standard
    (i.e. data validation in constructor, expect method X to exist, etc).

Is that correct?

I don't think prototype inheritance is a great fit for a language with
immutable data types. I think either generic functions or class
definitions are a better fit. Just because you /can/ use
/object/+lambdas to keep many things you want doesn't mean you /should/.

On 8/15/25 10:27 AM, Jordan Brown via Discuss wrote: >>> There's more than one style of OO inheritance.  What we've got here >>> is more like the JavaScript "prototype object" style of >>> inheritance:  you can take an object that you like, and make a >>> modified copy of it. >> The difference is you can't have super, which ECMAScript does have. > > I'm afraid my ES/JS experience, though pretty extensive, is for > work-legacy reasons primarily with a 2006-ish version of JS. At that > point it didn't have a `super`, but you could carefully build > something that more or less worked.  Have they added a real one? Yes. > But yes, that's a concern.  See the comment I just added to PR#6022. I commented there too. For the sake of maximum audience, I'll copy my questions/comments here: You've got some goals and are trying to make /object/ work for them. I think the goals are: 1. Replace hardcoded [1] and [CONSTANT] index into arrays by being able to name values by identifier 2. Be able to namespace functions, modules, and constants. 3. Be able to assert some piece of data as conforming to some standard (i.e. data validation in constructor, expect method X to exist, etc). Is that correct? I don't think prototype inheritance is a great fit for a language with immutable data types. I think either generic functions or class definitions are a better fit. Just because you /can/ use /object/+lambdas to keep many things you want doesn't mean you /should/.
GS
Guenther Sohler
Fri, Aug 15, 2025 5:51 PM

Apparently yes, sorry for that . Thanks for clarificaton.😜

Cory Cross openscad@corycross.org schrieb am Fr., 15. Aug. 2025, 19:47:

On 8/15/25 10:44 AM, Guenther Sohler via Discuss wrote:

Nope i disagree Here.
PythonSCAD is Not reinventing the wheel, its a Mix of leveraging existing
Technology, Bridging 2 worlds(Python and Scad are very interlinked) and
Quite some offering Features which openscad Refuse to have for Ages

I think you misread Jordan; he's saying adding too much OO stuff to the
OpenSCAD language is reinventing the wheel, and suggests PythonSCAD is
the better approach. I think he agrees with what you wrote.

Apparently yes, sorry for that . Thanks for clarificaton.😜 Cory Cross <openscad@corycross.org> schrieb am Fr., 15. Aug. 2025, 19:47: > On 8/15/25 10:44 AM, Guenther Sohler via Discuss wrote: > > Nope i disagree Here. > > PythonSCAD is Not reinventing the wheel, its a Mix of leveraging existing > > Technology, Bridging 2 worlds(Python and Scad are very interlinked) and > > Quite some offering Features which openscad Refuse to have for Ages > I think you misread Jordan; he's saying adding too much OO stuff to the > OpenSCAD language is reinventing the wheel, and suggests PythonSCAD is > the better approach. I think he agrees with what you wrote. > >
JB
Jordan Brown
Fri, Aug 15, 2025 5:59 PM

On 8/15/2025 7:47 PM, Cory Cross via Discuss wrote:

On 8/15/25 10:44 AM, Guenther Sohler via Discuss wrote:

Nope i disagree Here.
PythonSCAD is Not reinventing the wheel, its a Mix of leveraging
existing
Technology, Bridging 2 worlds(Python and Scad are very interlinked) and
Quite some offering Features which openscad Refuse to have for Ages

I think you misread Jordan; he's saying adding too much OO stuff to
the OpenSCAD language is reinventing the wheel, and suggests
PythonSCAD is the better approach. I think he agrees with what you wrote.

Yes.

If it wasn't for the sandboxing issue, I'd switch over to something
Python-based in a heartbeat.

Let's not further discuss PythonSCAD in this thread.  That's not what
it's about; this thread is about the evolution of the OpenSCAD language,
not about replacing it with a different language.  The only way that
replacing it with a different language is relevant is when and if we
decide that the rabbit hole is deep enough and say "that's as far as
we're going to go; if you want more go to another language".

On 8/15/2025 7:47 PM, Cory Cross via Discuss wrote: > On 8/15/25 10:44 AM, Guenther Sohler via Discuss wrote: >> Nope i disagree Here. >> PythonSCAD is Not reinventing the wheel, its a Mix of leveraging >> existing >> Technology, Bridging 2 worlds(Python and Scad are very interlinked) and >> Quite some offering Features which openscad Refuse to have for Ages > I think you misread Jordan; he's saying adding too much OO stuff to > the OpenSCAD language is reinventing the wheel, and suggests > PythonSCAD is the better approach. I think he agrees with what you wrote. Yes. If it wasn't for the sandboxing issue, I'd switch over to something Python-based in a heartbeat. Let's not further discuss PythonSCAD in this thread.  That's not what it's about; this thread is about the evolution of the OpenSCAD language, not about replacing it with a different language.  The only way that replacing it with a different language is relevant is when and if we decide that the rabbit hole is deep enough and say "that's as far as we're going to go; if you want more go to another language".
JB
Jordan Brown
Fri, Aug 15, 2025 6:20 PM

On 8/15/2025 7:51 PM, Cory Cross wrote:

On 8/15/25 10:27 AM, Jordan Brown via Discuss wrote:

[Has ES/JS] added a real [super]?

Yes.

Thanks.  I'll need to look at that.

You've got some goals and are trying to make /object/ work for them. I
think the goals are:

  1. Replace hardcoded [1] and [CONSTANT] index into arrays by being
    able to name values by identifier

  2. Be able to namespace functions, modules, and constants.

Yes.  And eventually maybe some of the other OO things like better
modularity and information hiding and sharing implementation and
definition for common behaviors.

  1. Be able to assert some piece of data as conforming to some standard
    (i.e. data validation in constructor, expect method X to exist, etc).

Maybe.  OpenSCAD is, like JavaScript and Python, a type-unsafe
language.  That's why I've been looking to those two for patterns to
follow.  That limits the amount that you can assert.

I actually prefer type-safe languages.  (Java isn't quite type-safe
enough for my preferences.)  I fantasize about a language where if you
tried to add meters to grams it would be a type error.  But they are
difficult for beginners, and the beginner audience is important here.

I don't think prototype inheritance is a great fit for a language with
immutable data types. I think either generic functions or class
definitions are a better fit. Just because you /can/ use
/object/+lambdas to keep many things you want doesn't mean you /should/.

Perhaps I just don't know enough OO theory.

I like JS because it has relatively simple building blocks, and yet you
can build very complex and powerful things with it. (Contrast with C++,
which adds so many syntactic and semantic constructs to C that it makes
me dizzy.)  JS has been what I've been primarily looking to in thinking
about adding OO-ish features to OpenSCAD.


BTW, note that I think that what we're talking about is of interest to
less than one in ten OpenSCAD users, and quite possibly less than one in
a hundred.  I expect that people like Adrian and Revar (authors of
BOSL2) are very interested, but I'm a pretty advanced user and I run out
of interest shortly after getting the basic data-only objects, and
almost completely once we have "this".  I don't think my projects would
benefit from "super".  (But, indirectly, if it helps build BOSL2++, it
helps me.)

Not that I don't have fun thinking about it... but that has nothing to
do with my use of OpenSCAD.

On 8/15/2025 7:51 PM, Cory Cross wrote: > On 8/15/25 10:27 AM, Jordan Brown via Discuss wrote: >> [Has ES/JS] added a real [super]? > Yes. Thanks.  I'll need to look at that. > You've got some goals and are trying to make /object/ work for them. I > think the goals are: > > 1. Replace hardcoded [1] and [CONSTANT] index into arrays by being > able to name values by identifier > > 2. Be able to namespace functions, modules, and constants. Yes.  And eventually maybe some of the other OO things like better modularity and information hiding and sharing implementation and definition for common behaviors. > 3. Be able to assert some piece of data as conforming to some standard > (i.e. data validation in constructor, expect method X to exist, etc). Maybe.  OpenSCAD is, like JavaScript and Python, a type-unsafe language.  That's why I've been looking to those two for patterns to follow.  That limits the amount that you can assert. I actually prefer type-safe languages.  (Java isn't quite type-safe enough for my preferences.)  I fantasize about a language where if you tried to add meters to grams it would be a type error.  But they are difficult for beginners, and the beginner audience is important here. > I don't think prototype inheritance is a great fit for a language with > immutable data types. I think either generic functions or class > definitions are a better fit. Just because you /can/ use > /object/+lambdas to keep many things you want doesn't mean you /should/. > Perhaps I just don't know enough OO theory. I like JS because it has relatively simple building blocks, and yet you can build very complex and powerful things with it. (Contrast with C++, which adds so many syntactic and semantic constructs to C that it makes me dizzy.)  JS has been what I've been primarily looking to in thinking about adding OO-ish features to OpenSCAD. --- BTW, note that I think that what we're talking about is of interest to less than one in ten OpenSCAD users, and quite possibly less than one in a hundred.  I expect that people like Adrian and Revar (authors of BOSL2) are very interested, but I'm a pretty advanced user and I run out of interest shortly after getting the basic data-only objects, and almost completely once we have "this".  I don't think my projects would benefit from "super".  (But, indirectly, if it helps build BOSL2++, it helps me.) Not that I don't have fun thinking about it... but that has nothing to do with my *use* of OpenSCAD.
TP
Torsten Paul
Fri, Aug 15, 2025 7:13 PM

On 15.08.25 20:20, Jordan Brown via Discuss wrote:

And eventually maybe some of the other OO things

What for? OO is for books and GUI libraries, otherwise it's
a mostly failed paradigm...

If we really want to offer a real OO language, I'd suggest
looking at an existing language and support that in addition
to the simple and approachable scripting we have now that
works for people who are not, and likely don't want to be
programmers.

ciao,
Torsten.

On 15.08.25 20:20, Jordan Brown via Discuss wrote: > And eventually maybe some of the other OO things What for? OO is for books and GUI libraries, otherwise it's a mostly failed paradigm... If we *really* want to offer a real OO language, I'd suggest looking at an existing language and support that in addition to the simple and approachable scripting we have now that works for people who are not, and likely don't want to be programmers. ciao, Torsten.
NS
Nathan Sokalski
Fri, Aug 15, 2025 7:51 PM

I am (and want to continue being) a programmer, in fact, writing the code, coming up with ways to make efficiently reusable functions & modules, manipulating the arrays & data, and other factors, are my favorite part of OpenSCAD. The reason I chose OpenSCAD instead of a more GUI-based designer was because I LIKE programming. Take away the code aspect, and I might end up ditching 3D printing as what is now one of the biggest parts of my life and hobbies. But back to the topic. Full-fledged languages are intended to be used for, well, everything, and that is why they don't have built-in geometry functions for things like 3D, they inherit classes & libraries. So developers somewhere down the line had to write them, but they still used the same core language. OpenSCAD is a language designed & optimized for one, and only one, purpose: 3D Printing. But I will also note that OpenSCAD isn't really a "programming" language at all, it is a "scripting" language. I am as happy as anybody about object() being implemented, but even a techie knows that things can go overboard. Are there features I would like to see that do not yet exist? Of course (maybe function/module overloading), but taking away the simplicity of OpenSCAD make it lose it's appeal. Right now, I would simply add the basic object(), it will take us a while to update all our existing code, so I think it is best to let us get started on that before obsessing over other stuff.

Nathan Sokalski
njsokalski@hotmail.commailto:njsokalski@hotmail.com


From: Torsten Paul via Discuss discuss@lists.openscad.org
Sent: Friday, August 15, 2025 3:13 PM
To: discuss@lists.openscad.org discuss@lists.openscad.org
Cc: Torsten Paul Torsten.Paul@gmx.de
Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function

On 15.08.25 20:20, Jordan Brown via Discuss wrote:

And eventually maybe some of the other OO things

What for? OO is for books and GUI libraries, otherwise it's
a mostly failed paradigm...

If we really want to offer a real OO language, I'd suggest
looking at an existing language and support that in addition
to the simple and approachable scripting we have now that
works for people who are not, and likely don't want to be
programmers.

ciao,
Torsten.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

I am (and want to continue being) a programmer, in fact, writing the code, coming up with ways to make efficiently reusable functions & modules, manipulating the arrays & data, and other factors, are my favorite part of OpenSCAD. The reason I chose OpenSCAD instead of a more GUI-based designer was because I LIKE programming. Take away the code aspect, and I might end up ditching 3D printing as what is now one of the biggest parts of my life and hobbies. But back to the topic. Full-fledged languages are intended to be used for, well, everything, and that is why they don't have built-in geometry functions for things like 3D, they inherit classes & libraries. So developers somewhere down the line had to write them, but they still used the same core language. OpenSCAD is a language designed & optimized for one, and only one, purpose: 3D Printing. But I will also note that OpenSCAD isn't really a "programming" language at all, it is a "scripting" language. I am as happy as anybody about object() being implemented, but even a techie knows that things can go overboard. Are there features I would like to see that do not yet exist? Of course (maybe function/module overloading), but taking away the simplicity of OpenSCAD make it lose it's appeal. Right now, I would simply add the basic object(), it will take us a while to update all our existing code, so I think it is best to let us get started on that before obsessing over other stuff. Nathan Sokalski njsokalski@hotmail.com<mailto:njsokalski@hotmail.com> ________________________________ From: Torsten Paul via Discuss <discuss@lists.openscad.org> Sent: Friday, August 15, 2025 3:13 PM To: discuss@lists.openscad.org <discuss@lists.openscad.org> Cc: Torsten Paul <Torsten.Paul@gmx.de> Subject: [OpenSCAD] Re: New feature in 2025.07.11: the object() function On 15.08.25 20:20, Jordan Brown via Discuss wrote: > And eventually maybe some of the other OO things What for? OO is for books and GUI libraries, otherwise it's a mostly failed paradigm... If we *really* want to offer a real OO language, I'd suggest looking at an existing language and support that in addition to the simple and approachable scripting we have now that works for people who are not, and likely don't want to be programmers. ciao, Torsten. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to discuss-leave@lists.openscad.org
JB
Jordan Brown
Fri, Aug 15, 2025 9:41 PM

On 8/15/2025 9:51 PM, Nathan Sokalski via Discuss wrote:

but taking away the simplicity of OpenSCAD make it lose it's appeal.

Indeed, one of the tricky questions is how to structure the
documentation and other materials so that a beginner can learn to do
simple things easily, but can progress to doing very advanced things.

We already have that problem; if you're not careful the fundamentals of
cube, cylinder, sphere, translate, rotate, union, difference,
intersection can get lost in discussions of special variables, children,
magic function operators, and so on.

On 8/15/2025 9:51 PM, Nathan Sokalski via Discuss wrote: > but taking away the simplicity of OpenSCAD make it lose it's appeal. Indeed, one of the tricky questions is how to structure the documentation and other materials so that a beginner can learn to do simple things easily, but can progress to doing very advanced things. We already have that problem; if you're not careful the fundamentals of cube, cylinder, sphere, translate, rotate, union, difference, intersection can get lost in discussions of special variables, children, magic function operators, and so on.
JB
Jordan Brown
Fri, Aug 15, 2025 11:01 PM

In my experience an extremely well defined order is paramount. Without
a defined order we could not even run our tests since it checks the
output of echo, which uses that order.

The testing issue can be addressed by sorting by key (probably in the
test) while printing the object.  That puts any performance cost in the
test, rather than in production.

I'd like objects to allow non-string keys in the future.

I think you want way too much here :-)

I want it all, and I want it now!

That would be a map and that is in my mind a very different beast then
an object.

I think we have two analogs:  JavaScript objects and either Python
objects or Python dictionaries.

One of the early commenters opined that what we were building was
more akin to dictionaries than anything else.  Coming more from a JS
background, I didn't agree, and didn't even really see the
distinction he was making, and I never got a clear answer as to what
the distinction was.

JS allows numbers as keys, but does it (at least sort of) by turning
them into strings.  You can set x[1] and then access it as x['1'], or
vice versa.

Python dictionaries allow numbers as keys, and they seem to retain their
number-ness.

I'm not sure, but I don't think that Python objects directly allow for
computed keys, so I find them a lot less interesting.

Our 'object' is what a struct or record is in most languages. It has
field names that are known to the code, it is a type.

No, the field names are not necessarily known to the program. That's why
you can use vector form when creating an object with object(), and why
you can access object members using o[expr] notation, to allow for keys
that are dynamically constructed.

In a map you have a mapping from one value to another, which is
fundamentally different.

How?  In both of them you have a key, and it has a corresponding value. 
Seems fundamentally the same.

 A map requires customization for the key types because for
performance you need to define a search algorithm like hashing or
ordered so you likely have several different map types. You probably
also will need counting maps, ordered maps, sorted maps, etc.

And yet I don't think that JS or Python have any of those things. 
(Though my Python knowledge is only mediocre and my JS knowledge is old.)

> In my experience an extremely well defined order is paramount. Without > a defined order we could not even run our tests since it checks the > output of echo, which uses that order. The testing issue can be addressed by sorting by key (probably in the test) while printing the object.  That puts any performance cost in the test, rather than in production. >> I'd like objects to allow non-string keys in the future. >> > I think you want way too much here :-) I want it all, and I want it now! > That would be a map and that is in my mind a very different beast then > an object. I think we have two analogs:  JavaScript objects and either Python objects or Python dictionaries. One of the early commenters opined that what we were building was more akin to dictionaries than anything else.  Coming more from a JS background, I didn't agree, and didn't even really see the distinction he was making, and I never got a clear answer as to what the distinction was. JS allows numbers as keys, but does it (at least sort of) by turning them into strings.  You can set x[1] and then access it as x['1'], or vice versa. Python dictionaries allow numbers as keys, and they seem to retain their number-ness. I'm not sure, but I don't think that Python objects directly allow for computed keys, so I find them a lot less interesting. > Our 'object' is what a struct or record is in most languages. It has > field names that are known to the code, it is a type. No, the field names are not necessarily known to the program. That's why you can use vector form when creating an object with object(), and why you can access object members using o[expr] notation, to allow for keys that are dynamically constructed. > In a map you have a mapping from one value to another, which is > fundamentally different. How?  In both of them you have a key, and it has a corresponding value.  Seems fundamentally the same. >  A map requires customization for the key types because for > performance you need to define a search algorithm like hashing or > ordered so you likely have several different map types. You probably > also will need counting maps, ordered maps, sorted maps, etc. And yet I don't think that JS or Python have any of those things.  (Though my Python knowledge is only mediocre and my JS knowledge is old.)
L
larry
Sat, Aug 16, 2025 12:37 AM

On Fri, 2025-08-15 at 19:27 +0200, Jordan Brown via Discuss wrote:

 There's more than one style of OO inheritance.  What we've got

here is more like the JavaScript "prototype object" style of
inheritance:  you can take an object that you like, and make a
modified copy of it.

 The difference is you can't have super, which ECMAScript does
have. 

I'm afraid my ES/JS experience, though pretty extensive, is for work-
legacy reasons primarily with a 2006-ish version of JS. At that point
it didn't have a super, but you could carefully build something
that more or less worked.  Have they added a real one?
But yes, that's a concern.  See the comment I just added to PR#6022.
A key question is just how far down the rabbit hole we want to g
Do we want to turn OpenSCAD into a full-scale OO environment?  Can we
even do that without losing its appeal to beginners?  There's already
an avenue for that, if you're willing to accept the security issues: 
PythonSCAD.  Language design and implementation is kind of fun, but
is reinventing the wheel, usually poorly.

I recommend OpenSCAD to beginners very often; whenever someone asks how
to model something or modify an STL. I do this because I find it easy
to understand, and because I do not get along with GUI-based CAD
Software.

Along with that I also recommend BOSL2, and point them to the Wiki. I
feel that OpenSCAD should remain OpenSCAD, and that if anything smacks
of Python or OO code, I would want to be able to continue using and
recommending it without having to struggle with those things.

I have programmed in many different languages in my career, but I am
absolutely done with learning a new one.

I have some ideas for super, but the rabbit hole keeps getting
deeper...

On Fri, 2025-08-15 at 19:27 +0200, Jordan Brown via Discuss wrote: >  There's more than one style of OO inheritance.  What we've got > > > here is more like the JavaScript "prototype object" style of > > > inheritance:  you can take an object that you like, and make a > > > modified copy of it. > >  The difference is you can't have super, which ECMAScript does > > have.  > I'm afraid my ES/JS experience, though pretty extensive, is for work- > legacy reasons primarily with a 2006-ish version of JS. At that point > it didn't have a `super`, but you could carefully build something > that more or less worked.  Have they added a real one? > But yes, that's a concern.  See the comment I just added to PR#6022. > A key question is just how far down the rabbit hole we want to g > Do we want to turn OpenSCAD into a full-scale OO environment?  Can we > even do that without losing its appeal to beginners?  There's already > an avenue for that, if you're willing to accept the security issues:  > PythonSCAD.  Language design and implementation is kind of fun, but > is reinventing the wheel, usually poorly. I recommend OpenSCAD to beginners very often; whenever someone asks how to model something or modify an STL. I do this because I find it easy to understand, and because I do not get along with GUI-based CAD Software. Along with that I also recommend BOSL2, and point them to the Wiki. I feel that OpenSCAD should remain OpenSCAD, and that if anything smacks of Python or OO code, I would want to be able to continue using and recommending it without having to struggle with those things. I have programmed in many different languages in my career, but I am absolutely done with learning a new one. > I have some ideas for `super`, but the rabbit hole keeps getting > deeper...
JB
Jordan Brown
Sat, Aug 16, 2025 5:47 AM

On 8/16/2025 2:37 AM, larry via Discuss wrote:

Along with that I also recommend BOSL2, and point them to the Wiki. I
feel that OpenSCAD should remain OpenSCAD, and that if anything smacks
of Python or OO code, I would want to be able to continue using and
recommending it without having to struggle with those things.

If what you were doing yesterday doesn't still work tomorrow, and it's
not incredibly obscure, that's a compatibility bug and we should almost
never allow it.

If a beginner trying to learn can see very much of the hypothetical OO
framework, that's a documentation failure, just as it's a failure if
they need to understand the behavior of special variables or children().

What a beginner might see is cleaner and simpler APIs to things like BOSL2.

On 8/16/2025 2:37 AM, larry via Discuss wrote: > Along with that I also recommend BOSL2, and point them to the Wiki. I > feel that OpenSCAD should remain OpenSCAD, and that if anything smacks > of Python or OO code, I would want to be able to continue using and > recommending it without having to struggle with those things. If what you were doing yesterday doesn't still work tomorrow, and it's not incredibly obscure, that's a compatibility bug and we should almost never allow it. If a beginner trying to learn can see very much of the hypothetical OO framework, that's a documentation failure, just as it's a failure if they need to understand the behavior of special variables or children(). What a beginner might see is cleaner and simpler APIs to things like BOSL2.
NH
nop head
Sat, Aug 16, 2025 9:46 AM

that's a compatibility bug and we should almost never allow it.

Well I think it was you that deprecated variables that begin with a number
that broke my library, so I have to use versions before that and the change
to the semantics of $variables means I need to use the last release for
command line builds.

On Sat, 16 Aug 2025 at 06:48, Jordan Brown via Discuss <
discuss@lists.openscad.org> wrote:

On 8/16/2025 2:37 AM, larry via Discuss wrote:

Along with that I also recommend BOSL2, and point them to the Wiki. I
feel that OpenSCAD should remain OpenSCAD, and that if anything smacks
of Python or OO code, I would want to be able to continue using and
recommending it without having to struggle with those things.

If what you were doing yesterday doesn't still work tomorrow, and it's not
incredibly obscure, that's a compatibility bug and we should almost never
allow it.

If a beginner trying to learn can see very much of the hypothetical OO
framework, that's a documentation failure, just as it's a failure if they
need to understand the behavior of special variables or children().

What a beginner might see is cleaner and simpler APIs to things like BOSL2.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

> that's a compatibility bug and we should almost never allow it. Well I think it was you that deprecated variables that begin with a number that broke my library, so I have to use versions before that and the change to the semantics of $variables means I need to use the last release for command line builds. On Sat, 16 Aug 2025 at 06:48, Jordan Brown via Discuss < discuss@lists.openscad.org> wrote: > On 8/16/2025 2:37 AM, larry via Discuss wrote: > > Along with that I also recommend BOSL2, and point them to the Wiki. I > feel that OpenSCAD should remain OpenSCAD, and that if anything smacks > of Python or OO code, I would want to be able to continue using and > recommending it without having to struggle with those things. > > If what you were doing yesterday doesn't still work tomorrow, and it's not > incredibly obscure, that's a compatibility bug and we should almost never > allow it. > > If a beginner trying to learn can see very much of the hypothetical OO > framework, that's a documentation failure, just as it's a failure if they > need to understand the behavior of special variables or children(). > > What a beginner might see is cleaner and simpler APIs to things like BOSL2. > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
CC
Cory Cross
Sat, Aug 16, 2025 11:33 AM

On 8/15/25 11:20 AM, Jordan Brown wrote:

I don't think prototype inheritance is a great fit for a language
with immutable data types. I think either generic functions or class
definitions are a better fit. Just because you /can/ use
/object/+lambdas to keep many things you want doesn't mean you /should/.

Perhaps I just don't know enough OO theory.

I like JS because it has relatively simple building blocks, and yet
you can build very complex and powerful things with it. (Contrast with
C++, which adds so many syntactic and semantic constructs to C that it
makes me dizzy.)  JS has been what I've been primarily looking to in
thinking about adding OO-ish features to OpenSCAD.


BTW, note that I think that what we're talking about is of interest to
less than one in ten OpenSCAD users, and quite possibly less than one
in a hundred.  I expect that people like Adrian and Revar (authors of
BOSL2) are very interested, but I'm a pretty advanced user and I run
out of interest shortly after getting the basic data-only objects, and
almost completely once we have "this".  I don't think my projects
would benefit from "super".  (But, indirectly, if it helps build
BOSL2++, it helps me.)

I don't think OpenSCAD should have complex OO, which is what worries me
about the this discussion and all the complexities of scoping and such
you have brought up. Here's what I've been thinking about:

In the context of the 3 problems I stated (maps/structs, namespacing,
data compartmentalization). Add namespaces. Define
maps/structs/records/whatever-you-call-it as only containing Values
(ints, lists, lambdas, other records, etc) and having one parent. Both
of these things can be implemented simply (name mangling for namespaces,
records can be a hash map + parent lookup on miss). Both of these have
immediate benefits, both are easy to explain to people. They both have
established ways of being optimized for speed.

That just leaves "how do I do OO?" We know from Java's history that long
inheritance chains are bad and people rarely change out implementations
and OO didn't make this any easier.
But then that isn't the real request. The real request is "How do I make
it less likely to make mistakes and spend less time writing code to get
my result?"

Go's philosophy is, in part, to notice people don't spend much time
typing, so don't focus on minimizing key strokes, instead focus on
readability and unit testability, even if that means you write a lot of
redundant things over and over again.
Go has classes ("structs") which are allowed to have up to one parent
and must be a concrete type.
It also has "interfaces" which is a list of method signatures and has
its own methods. If your class has all the method signatures of the
interface, you can cast it to the interface and call the interface's
methods' on it.

The single-inheritance is a good choice. The only inheriting from
concrete types is good. We learned from Java.

However, Go is very imperative. Further lessons are not helpful to a
language with immutable values.

So, what do languages with immutable values usually have in common?
Elixir/Erlang, Haskell, Ocaml, etc: Pattern matching.

And if you combine functional language, objects, and pattern matching,
you get generic functions. The exact syntax details of the following are
mostly not important.

namespace bosl2 { namespace threading { namespace MyClass {
   obj = struct(some_default=2);

   // Constructors can be just normal functions
   function new(something,some_default) =
     assert(!is_undef(something))
     let (
       args = [["something" something]],
       args = concat(args,is_undef(some_default) ? [] : [["some_default" 
some_default]])
     ) struct(obj, args);

   // Methods are functions and modules.
   function sum(o is obj) = o.something+o.some_default;
   module fat_cylinder(o is obj) { cylinder(d=o->sum()*2); }

   // Constructors can also be method functions
   function coerce(l is list) = new(l[0],l[1]);
} // namespace MyClass

// Modules/functions can be generic on anybody's class:
module nut(o is MyClass::obj, normal_arg) {
   if(normal_arg) {
     cylinder(d=MyClass::sum(o));
   } else {
     cylinder(d=o->sum()); // -> walks from object through its ancestors 
for the first matching method in each object's creation namespace
     o->fat_cylinder();
   }
}

module nut(o is list, a_different_arg_is_possible) {
   obj_o = MyClass::coerce(o);
   assert(!is_undef(obj_o));
   nut(o, normal_arg=a_different_arg_is_possible);
}
}}

// backward compatibility
module which_takes_complex_thing(existing, arg, list) {
   assert(on arg);
bosl2::threading::nut([existing, arg*2, list]);
}

Okay, so if you've got the basics of how this works, how does this
actually help?

Let's consider the BOSL2 Threading library (surprise, I bet you wouldn't
have guessed).

You have very long and repeated argument lists
https://github.com/BelfrySCAD/BOSL2/blob/d252f781232a9262583806fe05bbb8529c108899/threading.scad#L384.
And we could say, from Go, that's it's okay to type things repeatedly,
but it's really easy to screw up. So how can we avoid this?

The builder pattern can help. In our hypothetical class hierarchy,
threaded_nut_builder inherits from generic_threaded_nut_builder.

bosl2::threading::nut_builder::new(required, args,
here)->optional_generic_arg(its_value)->reify();

From a user's standpoint, this used to be:

threaded_nut(required, args, here, optional_generic_arc=its_value);

From a user standpoint, it's hardly different. We might even have
syntax sugar to simplify ::new and ->reify().

But from a maintainer's standpoint, you no longer have to edit an
ever-growing list of places for new arguments to generic_threaded_nut.
threaded_nut_builder's reify() module only has the same dozen lines of
computation before chucking it up to generic_threaded_nut. You have a
private namespace for any logic you want to unit test. No semantics of
the language are changed regarding types, handling missing args, etc.
You can maintain 100% compatibility with the existing API while adding
options/features to the new API. You can incrementally update the
codebase a single module or function at a time, so it's easy to adopt.
It's focused around answering the question "How do I make it less likely
to make mistakes and spend less time writing code to get my result?"
from real lessons.

  • Cory Cross
On 8/15/25 11:20 AM, Jordan Brown wrote: >> I don't think prototype inheritance is a great fit for a language >> with immutable data types. I think either generic functions or class >> definitions are a better fit. Just because you /can/ use >> /object/+lambdas to keep many things you want doesn't mean you /should/. >> > Perhaps I just don't know enough OO theory. > > I like JS because it has relatively simple building blocks, and yet > you can build very complex and powerful things with it. (Contrast with > C++, which adds so many syntactic and semantic constructs to C that it > makes me dizzy.)  JS has been what I've been primarily looking to in > thinking about adding OO-ish features to OpenSCAD. > > --- > > BTW, note that I think that what we're talking about is of interest to > less than one in ten OpenSCAD users, and quite possibly less than one > in a hundred.  I expect that people like Adrian and Revar (authors of > BOSL2) are very interested, but I'm a pretty advanced user and I run > out of interest shortly after getting the basic data-only objects, and > almost completely once we have "this".  I don't think my projects > would benefit from "super".  (But, indirectly, if it helps build > BOSL2++, it helps me.) I don't think OpenSCAD should have complex OO, which is what worries me about the `this` discussion and all the complexities of scoping and such you have brought up. Here's what I've been thinking about: In the context of the 3 problems I stated (maps/structs, namespacing, data compartmentalization). Add namespaces. Define maps/structs/records/whatever-you-call-it as only containing Values (ints, lists, lambdas, other records, etc) and having one parent. Both of these things can be implemented simply (name mangling for namespaces, records can be a hash map + parent lookup on miss). Both of these have immediate benefits, both are easy to explain to people. They both have established ways of being optimized for speed. That just leaves "how do I do OO?" We know from Java's history that long inheritance chains are bad and people rarely change out implementations and OO didn't make this any easier. But then that isn't the real request. The real request is "How do I make it less likely to make mistakes and spend less time writing code to get my result?" Go's philosophy is, in part, to notice people don't spend much time typing, so don't focus on minimizing key strokes, instead focus on readability and unit testability, even if that means you write a lot of redundant things over and over again. Go has classes ("structs") which are allowed to have up to one parent and must be a concrete type. It also has "interfaces" which is a list of method signatures and has its own methods. If your class has all the method signatures of the interface, you can cast it to the interface and call the interface's methods' on it. The single-inheritance is a good choice. The only inheriting from concrete types is good. We learned from Java. However, Go is very imperative. Further lessons are not helpful to a language with immutable values. So, what do languages with immutable values usually have in common? Elixir/Erlang, Haskell, Ocaml, etc: Pattern matching. And if you combine functional language, objects, and pattern matching, you get generic functions. The exact syntax details of the following are mostly not important. ``` namespace bosl2 { namespace threading { namespace MyClass {   obj = struct(some_default=2);   // Constructors can be just normal functions   function new(something,some_default) =     assert(!is_undef(something))     let (       args = [["something" something]],       args = concat(args,is_undef(some_default) ? [] : [["some_default" some_default]])     ) struct(obj, args);   // Methods are functions and modules.   function sum(o is obj) = o.something+o.some_default;   module fat_cylinder(o is obj) { cylinder(d=o->sum()*2); }   // Constructors can also be method functions   function coerce(l is list) = new(l[0],l[1]); } // namespace MyClass // Modules/functions can be generic on anybody's class: module nut(o is MyClass::obj, normal_arg) {   if(normal_arg) {     cylinder(d=MyClass::sum(o));   } else {     cylinder(d=o->sum()); // -> walks from object through its ancestors for the first matching method in each object's creation namespace     o->fat_cylinder();   } } module nut(o is list, a_different_arg_is_possible) {   obj_o = MyClass::coerce(o);   assert(!is_undef(obj_o));   nut(o, normal_arg=a_different_arg_is_possible); } }} // backward compatibility module which_takes_complex_thing(existing, arg, list) {   assert(on arg); bosl2::threading::nut([existing, arg*2, list]); } ``` Okay, so if you've got the basics of how this works, how does this actually help? Let's consider the BOSL2 Threading library (surprise, I bet you wouldn't have guessed). You have very long and repeated argument lists <https://github.com/BelfrySCAD/BOSL2/blob/d252f781232a9262583806fe05bbb8529c108899/threading.scad#L384>. And we could say, from Go, that's it's okay to type things repeatedly, but it's really easy to screw up. So how can we avoid this? The builder pattern can help. In our hypothetical class hierarchy, threaded_nut_builder inherits from generic_threaded_nut_builder. bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); From a user's standpoint, this used to be: threaded_nut(required, args, here, optional_generic_arc=its_value); From a user standpoint, it's hardly different. We might even have syntax sugar to simplify ::new and ->reify(). But from a maintainer's standpoint, you no longer have to edit an ever-growing list of places for new arguments to generic_threaded_nut. threaded_nut_builder's reify() module only has the same dozen lines of computation before chucking it up to generic_threaded_nut. You have a private namespace for any logic you want to unit test. No semantics of the language are changed regarding types, handling missing args, etc. You can maintain 100% compatibility with the existing API while adding options/features to the new API. You can incrementally update the codebase a single module or function at a time, so it's easy to adopt. It's focused around answering the question "How do I make it less likely to make mistakes and spend less time writing code to get my result?" from real lessons. - Cory Cross
JB
Jordan Brown
Sat, Aug 16, 2025 3:49 PM

[ Changed the subject to more accurately represent the topic. ]

[ I don't have a strong opinion on whether we should have this
discussion on the mailing list or on Github, but it should probably only
be one of the two. ]

On 8/16/2025 1:33 PM, Cory Cross wrote:

I don't think OpenSCAD should have complex OO,

I'm not sure, but I think what you suggested is approximately three
times as complex as anything we've discussed before...

You're introducing type safety, at least sort of, and I have very mixed
feelings about that.  On the one hand I like type safety. On the other
hand, it's entirely new to OpenSCAD.

which is what worries me about the this discussion and all the
complexities of scoping and such you have brought up.

You propose an answer to the "this" questions; you just aren't explicit
about it.  The answer that you propose is that "this" must be explicitly
specified as the first parameter to the method.  Yes, that addresses the
scoping problems.  We didn't like that it adds boilerplate and makes the
argument list for a call be different from the parameter list for a
declaration, and so were trying to avoid it.  (But, combined with the
idea that calling a method automatically includes the object as the
first argument, and calling that same function not as a method does not,
it does tie up a number of loose ends.)

Define maps/structs/records/whatever-you-call-it as only containing
Values (ints, lists, lambdas, other records, etc) and having one parent.

Why do you need this "parent" concept?  I don't think it adds anything
to what we're got today with object(), and it makes iteration behavior
less obvious.  (And seems to get drop the ability to merge objects.)  I
think maybe you expect it to make super more possible, but that would
seem to require mechanisms not yet discussed.

(Note that object() has a feature that looks sort of like your
parentage, but does not establish any sort of parentage relationship;
instead, it copies the object into the object being constructed.)

name mangling for namespaces

Name mangling is for people who have linkers that don't support
namespaces :-)

Go's philosophy is, in part, to notice people don't spend much time
typing, so don't focus on minimizing key strokes, instead focus on
readability and unit testability, even if that means you write a lot
of redundant things over and over again.

I'm not familiar with Go, so I can only respond to your comment. If you
write a lot of redundant things, that reduces readability and increases
the opportunity for error.  Are you suggesting that DRY
https://en.wikipedia.org/wiki/Don%27t_repeat_yourself is not a good
philosophy?

I certainly agree that words are better than punctuation, except in the
most common of cases.  (But:  words can intrude on namespace, where
punctuation doesn't.)

  function sum(o is obj) = o.something+o.some_default;
  module fat_cylinder(o is obj) { cylinder(d=o->sum()*2); }

I notice that because you do not have methods being members, you had to
introduce "->" to distinguish between them, and that sum() sometimes
takes one argument and sometimes takes none. (Yes, I see the pattern,
but it still seems weird.)

module nut(o is MyClass::obj, normal_arg) {

I am confused about whether obj is a variable or a data type, or what it
means if it is both.  (Maybe it means "has obj in its parentage chain".)

module nut(o is list, a_different_arg_is_possible) {
  obj_o = MyClass::coerce(o);
  assert(!is_undef(obj_o));
  nut(o, normal_arg=a_different_arg_is_possible);
}
}}

I think you meant that call to be to MyClass::nut().

  assert(on arg);

Is this a typo?  If not, I have no idea what it means.

bosl2::threading::nut_builder::new(required, args,
here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument
handling, but I'm not understanding what that means at all.  Partly
that's presentation; is this intended to be how the library would say
something, or how the caller would invoke the function?

If the former, I don't understand what it means.  If the latter, are you
seriously suggesting this as a replacement for

threaded_nut(required, args, here, optional_generic_arc=its_value);

?

It seems like we were struggling with one or two questions, and you've
implicitly answered that question but then added several more.  That's
not to say that they might not be good ideas, but they make it harder to
structure a discussion so as to knock down one question at a time.

[ Changed the subject to more accurately represent the topic. ] [ I don't have a strong opinion on whether we should have this discussion on the mailing list or on Github, but it should probably only be one of the two. ] On 8/16/2025 1:33 PM, Cory Cross wrote: > I don't think OpenSCAD should have complex OO, I'm not sure, but I think what you suggested is approximately three times as complex as anything we've discussed before... You're introducing type safety, at least sort of, and I have very mixed feelings about that.  On the one hand I like type safety. On the other hand, it's entirely new to OpenSCAD. > which is what worries me about the `this` discussion and all the > complexities of scoping and such you have brought up. You propose an answer to the "this" questions; you just aren't explicit about it.  The answer that you propose is that "this" must be explicitly specified as the first parameter to the method.  Yes, that addresses the scoping problems.  We didn't like that it adds boilerplate and makes the argument list for a call be different from the parameter list for a declaration, and so were trying to avoid it.  (But, combined with the idea that calling a method automatically includes the object as the first argument, and calling that same function not as a method does not, it does tie up a number of loose ends.) > Define maps/structs/records/whatever-you-call-it as only containing > Values (ints, lists, lambdas, other records, etc) and having one parent. Why do you need this "parent" concept?  I don't think it adds anything to what we're got today with object(), and it makes iteration behavior less obvious.  (And seems to get drop the ability to merge objects.)  I think maybe you expect it to make `super` more possible, but that would seem to require mechanisms not yet discussed. (Note that object() has a feature that looks sort of like your parentage, but does not establish any sort of parentage relationship; instead, it copies the object into the object being constructed.) > name mangling for namespaces Name mangling is for people who have linkers that don't support namespaces :-) > Go's philosophy is, in part, to notice people don't spend much time > typing, so don't focus on minimizing key strokes, instead focus on > readability and unit testability, even if that means you write a lot > of redundant things over and over again. I'm not familiar with Go, so I can only respond to your comment. If you write a lot of redundant things, that reduces readability and increases the opportunity for error.  Are you suggesting that DRY <https://en.wikipedia.org/wiki/Don%27t_repeat_yourself> is not a good philosophy? I certainly agree that words are better than punctuation, except in the most common of cases.  (But:  words can intrude on namespace, where punctuation doesn't.) >   function sum(o is obj) = o.something+o.some_default; >   module fat_cylinder(o is obj) { cylinder(d=o->sum()*2); } I notice that because you do not have methods being members, you had to introduce "->" to distinguish between them, and that sum() sometimes takes one argument and sometimes takes none. (Yes, I see the pattern, but it still seems weird.) > module nut(o is MyClass::obj, normal_arg) { I am confused about whether obj is a variable or a data type, or what it means if it is both.  (Maybe it means "has obj in its parentage chain".) > > module nut(o is list, a_different_arg_is_possible) { >   obj_o = MyClass::coerce(o); >   assert(!is_undef(obj_o)); >   nut(o, normal_arg=a_different_arg_is_possible); > } > }} I think you meant that call to be to MyClass::nut(). >   assert(on arg); Is this a typo?  If not, I have no idea what it means. > bosl2::threading::nut_builder::new(required, args, > here)->optional_generic_arg(its_value)->reify(); I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for threaded_nut(required, args, here, optional_generic_arc=its_value); ? It seems like we were struggling with one or two questions, and you've implicitly answered that question but then added several more.  That's not to say that they might not be good ideas, but they make it harder to structure a discussion so as to knock down one question at a time.
CC
Cory Cross
Mon, Aug 18, 2025 9:54 PM

On 8/16/25 8:49 AM, Jordan Brown wrote:

Go's philosophy is, in part, to notice people don't spend much time
typing, so don't focus on minimizing key strokes, instead focus on
readability and unit testability, even if that means you write a lot
of redundant things over and over again.

I'm not familiar with Go, so I can only respond to your comment.  If
you write a lot of redundant things, that reduces readability and
increases the opportunity for error.  Are you suggesting that DRY
https://en.wikipedia.org/wiki/Don%27t_repeat_yourself is not a good
philosophy?

I am saying the Go philosophy is about not adding features to reduce the
amount of characters written. You see this a million times:

thing, err := function_which_returns_thing_or_error()
if err != nil {
  return nil, err
}
thing2, err := function_using_thing(thing)
if err != nil {
  return nil, err
}

In Rust and others, you have the ? operator which you can tack onto
anything which can return either a value or an error, and simply return
the error from the current function if the thing is an error.

thing2 := function_using_thing(function_which_returns_thing_or_error()?)?

Go is not going to add that.

The Go philosophy is also that if you can write something with a for
loop in 5 lines, then there shouldn't be a function for it. So if you
wanted type safe accesses for a data structure not builtin, you had to
write the methods yourself (i.e. contains()). (Luckily generics finally
got added). I don't like this.

I do agree with WET (write everything twice) instead of DRY, but I have
a lot of issues with some aspects of Go (especially that it values unit
testability, but being able to write integration tests makes your code
garbage (you can't mock concrete types from other packages, so you have
to use interfaces for every library you use, if you want to test with a
fake or mock, it's Java-esque in indirection)).

On 8/16/25 8:49 AM, Jordan Brown wrote: >> Go's philosophy is, in part, to notice people don't spend much time >> typing, so don't focus on minimizing key strokes, instead focus on >> readability and unit testability, even if that means you write a lot >> of redundant things over and over again. > > I'm not familiar with Go, so I can only respond to your comment.  If > you write a lot of redundant things, that reduces readability and > increases the opportunity for error.  Are you suggesting that DRY > <https://en.wikipedia.org/wiki/Don%27t_repeat_yourself> is not a good > philosophy? > I am saying the Go philosophy is about not adding features to reduce the amount of characters written. You see this a million times: thing, err := function_which_returns_thing_or_error() if err != nil {   return nil, err } thing2, err := function_using_thing(thing) if err != nil {   return nil, err } In Rust and others, you have the ? operator which you can tack onto anything which can return either a value or an error, and simply return the error from the current function if the thing is an error. thing2 := function_using_thing(function_which_returns_thing_or_error()?)? Go is not going to add that. The Go philosophy is also that if you can write something with a for loop in 5 lines, then there shouldn't be a function for it. So if you wanted type safe accesses for a data structure not builtin, you had to write the methods yourself (i.e. contains()). (Luckily generics finally got added). I don't like this. I do agree with WET (write everything twice) instead of DRY, but I have a lot of issues with some aspects of Go (especially that it values unit testability, but being able to write integration tests makes your code garbage (you can't mock concrete types from other packages, so you have to use interfaces for every library you use, if you want to test with a fake or mock, it's Java-esque in indirection)).
CC
Cory Cross
Mon, Aug 18, 2025 10:22 PM

On 8/16/25 8:49 AM, Jordan Brown wrote:

On 8/16/2025 1:33 PM, Cory Cross wrote:

I don't think OpenSCAD should have complex OO,

I'm not sure, but I think what you suggested is approximately three
times as complex as anything we've discussed before...

There's simple-to-use and simple-to-implement. What I think you are
suggesting is simple-to-implement. I'm suggesting simple-to-use instead :).

You're introducing type safety, at least sort of, and I have very
mixed feelings about that.  On the one hand I like type safety. On the
other hand, it's entirely new to OpenSCAD.

No, no type safety. Just pattern matching and checking pointer equivalence.

which is what worries me about the this discussion and all the
complexities of scoping and such you have brought up.

You propose an answer to the "this" questions; you just aren't
explicit about it.  The answer that you propose is that "this" must be
explicitly specified as the first parameter to the method.  Yes, that
addresses the scoping problems.  We didn't like that it adds
boilerplate and makes the argument list for a call be different from
the parameter list for a declaration, and so were trying to avoid it. 
(But, combined with the idea that calling a method automatically
includes the object as the first argument, and calling that same
function not as a method does not, it does tie up a number of loose
ends.
)

(bolding mine) :-)

Define maps/structs/records/whatever-you-call-it as only containing
Values (ints, lists, lambdas, other records, etc) and having one parent.

Why do you need this "parent" concept?  I don't think it adds anything
to what we're got today with object(), and it makes iteration behavior
less obvious.  (And seems to get drop the ability to merge objects.)

You have object-related methods like "has_key". Instead of putting them
in the global namespace, you put it in the equivalent of Java's Object
(i.e. the object that all objects eventually descend from). This is also
the logical place for any other related methods like iterators for just
keys, just values, or key-value pairs [key value]; or a tostring method;
etc. If you think you're going to have methods and people won't want
these, well, they're going to!

With immutable data types, parentage as pointer or merge is actually an
implementation detail. The semantics should be the same, otherwise
methods on parents are quite limited if they can't reference values
overridden in a child.

I think maybe you expect it to make super more possible, but that
would seem to require mechanisms not yet discussed.

I think the lesson learned from trying to change parent libraries is
using super() or explicitly naming the superclass is not a consequential
cost of refactoring, so super() does not help enough over explicitly
naming the desired method.
So no super(). Just use parentClassNamespace::method(self).

...
I am confused about whether obj is a variable or a data type, or what
it means if it is both.  (Maybe it means "has obj in its parentage
chain".)

"has obj in its parentage chain" <= bingo. Eliminates the need for
class keyword by using namespaces. Simpler.

The parent relationship enables -> to work without keeping a per-object
vtable of all inherited methods. You could just as well collect all
methods (or at least all namespaces) on object creation, but it should
be more efficient to not. Or, at the very least, easier to implement :).
Caching should solve most theoretical performance issues.

(Note that object() has a feature that looks sort of like your
parentage, but does not establish any sort of parentage relationship;
instead, it copies the object into the object being constructed.)

I think the merge semantics should be retained regarding the keys and
values.

  function sum(o is obj) = o.something+o.some_default;
  module fat_cylinder(o is obj) { cylinder(d=o->sum()*2); }

I notice that because you do not have methods being members, you had
to introduce "->" to distinguish between them, and that sum()
sometimes takes one argument and sometimes takes none. (Yes, I see the
pattern, but it still seems weird.)

This also eliminates the need to make first-class modules i.e. being
able to define them anywhere but the top-level or assign them as
variables. This is a lot of implementation complexity that can be
avoided and isn't currently being discussed.

This is like Python if you want to call an explicit superclass method,
you add this-equivalent yourself: SuperClassName.method_name(self, args)

The reason for -> is, yes, for looking up in the namespace and not
members. This, among other things, allows you to release just object()
as it currently works.

module nut(o is list, a_different_arg_is_possible) {
  obj_o = MyClass::coerce(o);
  assert(!is_undef(obj_o));
  nut(o, normal_arg=a_different_arg_is_possible);
}
}}

I think you meant that call to be to MyClass::nut().

Nope, the other "nut()" is also a module in bosl2::threading. It's
placed there as a demonstration that generic functions/modules can
dispatch on any type in any namespace (unlike a Python method: a Python
method can only be dispatched on objects from its class or a class which
descends from it).

  assert(on arg);

Is this a typo?  If not, I have no idea what it means.

typo/just being vague. Just meant "this is where you'd write asserts"

I'll answer the rest in another email, struggling a little to finish it up.

-Cory Cross

On 8/16/25 8:49 AM, Jordan Brown wrote: > On 8/16/2025 1:33 PM, Cory Cross wrote: >> I don't think OpenSCAD should have complex OO, > I'm not sure, but I think what you suggested is approximately three > times as complex as anything we've discussed before... There's simple-to-use and simple-to-implement. What I think you are suggesting is simple-to-implement. I'm suggesting simple-to-use instead :). > You're introducing type safety, at least sort of, and I have very > mixed feelings about that.  On the one hand I like type safety. On the > other hand, it's entirely new to OpenSCAD. No, no type safety. Just pattern matching and checking pointer equivalence. >> which is what worries me about the `this` discussion and all the >> complexities of scoping and such you have brought up. > > You propose an answer to the "this" questions; you just aren't > explicit about it.  The answer that you propose is that "this" must be > explicitly specified as the first parameter to the method.  Yes, that > addresses the scoping problems.  We didn't like that it adds > boilerplate and makes the argument list for a call be different from > the parameter list for a declaration, and so were trying to avoid it.  > (But, combined with the idea that calling a method automatically > includes the object as the first argument, and calling that same > function not as a method does not, it does *tie up a number of loose > ends.*) (bolding mine) :-) >> Define maps/structs/records/whatever-you-call-it as only containing >> Values (ints, lists, lambdas, other records, etc) and having one parent. > > Why do you need this "parent" concept?  I don't think it adds anything > to what we're got today with object(), and it makes iteration behavior > less obvious.  (And seems to get drop the ability to merge objects.) You have object-related methods like "has_key". Instead of putting them in the global namespace, you put it in the equivalent of Java's Object (i.e. the object that all objects eventually descend from). This is also the logical place for any other related methods like iterators for just keys, just values, or key-value pairs [key value]; or a tostring method; etc. If you think you're going to have methods and people won't want these, well, they're going to! With immutable data types, parentage as pointer or merge is actually an implementation detail. The semantics should be the same, otherwise methods on parents are quite limited if they can't reference values overridden in a child. > I think maybe you expect it to make `super` more possible, but that > would seem to require mechanisms not yet discussed. I think the lesson learned from trying to change parent libraries is using super() or explicitly naming the superclass is not a consequential cost of refactoring, so super() does not help enough over explicitly naming the desired method. So no super(). Just use parentClassNamespace::method(self). > ... > I am confused about whether obj is a variable or a data type, or what > it means if it is both.  (Maybe it means "has obj in its parentage > chain".) "has obj in its parentage chain" <= bingo. Eliminates the need for `class` keyword by using namespaces. Simpler. The parent relationship enables -> to work without keeping a per-object vtable of all inherited methods. You could just as well collect all methods (or at least all namespaces) on object creation, but it should be more efficient to not. Or, at the very least, easier to implement :). Caching should solve most theoretical performance issues. > (Note that object() has a feature that looks sort of like your > parentage, but does not establish any sort of parentage relationship; > instead, it copies the object into the object being constructed.) I think the merge semantics should be retained regarding the keys and values. >>   function sum(o is obj) = o.something+o.some_default; >>   module fat_cylinder(o is obj) { cylinder(d=o->sum()*2); } > > I notice that because you do not have methods being members, you had > to introduce "->" to distinguish between them, and that sum() > sometimes takes one argument and sometimes takes none. (Yes, I see the > pattern, but it still seems weird.) This also eliminates the need to make first-class modules i.e. being able to define them anywhere but the top-level or assign them as variables. This is a lot of implementation complexity that can be avoided and isn't currently being discussed. This is like Python if you want to call an explicit superclass method, you add `this`-equivalent yourself: `SuperClassName.method_name(self, args)` The reason for `->` is, yes, for looking up in the namespace and not members. This, among other things, allows you to release just `object()` as it currently works. >> module nut(o is list, a_different_arg_is_possible) { >>   obj_o = MyClass::coerce(o); >>   assert(!is_undef(obj_o)); >>   nut(o, normal_arg=a_different_arg_is_possible); >> } >> }} > > I think you meant that call to be to MyClass::nut(). Nope, the other "nut()" is also a module in bosl2::threading. It's placed there as a demonstration that generic functions/modules can dispatch on any type in any namespace (unlike a Python method: a Python method can only be dispatched on objects from its class or a class which descends from it). >>   assert(on arg); > > Is this a typo?  If not, I have no idea what it means. typo/just being vague. Just meant "this is where you'd write asserts" I'll answer the rest in another email, struggling a little to finish it up. -Cory Cross
CC
Cory Cross
Tue, Aug 19, 2025 5:47 AM

On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args,
here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument
handling, but I'm not understanding what that means at all.  Partly
that's presentation; is this intended to be how the library would say
something, or how the caller would invoke the function?

If the former, I don't understand what it means.  If the latter, are
you seriously suggesting this as a replacement for

 threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's
better for the user, but because it's better for the maintainers and not
worse for the users. (I would assume we'd add using bosl2::threading
to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my
code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO
approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use
objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from
50 lines
https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/threading.scad#L1242
to 11:

module buttress_threaded_nut(kwargs) {
    profile = [
        [  -1/2, -0.77],
        [ -7/16, -0.75],
        [  5/16,  0],
        [  7/16,  0],
        [  7/16, -0.75],
        [  1/ 2, -0.77],
    ];
    generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name
typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
    let ( profile = [
        [  -1/2, -0.77],
        [ -7/16, -0.75],
        [  5/16,  0],
        [  7/16,  0],
        [  7/16, -0.75],
        [  1/ 2, -0.77],
    ])
    struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and
positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
    assert(is_list(profile)) // And other tests independent of other values
    struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
    assert(/* tests related to the parameters*/)
    let(
        l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
           is_undef(spin) ? [] : [["spin", spin]],
           is_undef(orient) ? [] : [["orient", orient]])
        )
    struct(o,l);

Okay, not in love with the repetition in there. But there are some
improvements here:

  1. Some validation can now stop cluttering the top of
    https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/shapes3d.scad#L353
    so many functions/modules.
  2. The get_radius
    https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/shapes3d.scad#L2461
    function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so
    many args every
    https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/shapes3d.scad#L2469
    single
    https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/threading.scad#L2067
    time
    https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/walls.scad#L69
    and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace
of positioning? I didn't intend it to, but it would do the wrong thing
as written; only the calls in the new() methods should add the namespace
to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
    let ( profile = [
        [  -1/2, -0.77],
        [ -7/16, -0.75],
        [  5/16,  0],
        [  7/16,  0],
        [  7/16, -0.75],
        [  1/ 2, -0.77],
    ])
    object(new_generic_threaded_rod_builder().set_profile(profile), /*
all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
    assert(is_list(profile)) // And other tests independent of other values
    object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
    assert(/* tests related to the parameters*/)
    let(
        l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
           is_undef(spin) ? [] : [["spin", spin]],
           is_undef(orient) ? [] : [["orient", orient]])
        )
    struct(this,l);

module reify_buttress_threaded_nut(obj) {
    reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
    reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are
annoying. For this usage the inability to write your own generic methods
dispatching on other object types does not hinder anything. The shared
namespace means methods and values must have unique names. I think this
is supposed to solve name conflicts by having you only need one unique
name per file and you put all your constants in there? And methods, I
guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
    top_level_constant = 27,
    function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
    bosl2_threading.top_level_constant; // for whatever reason
    reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.

On 8/16/25 8:49 AM, Jordan Brown wrote: >> bosl2::threading::nut_builder::new(required, args, >> here)->optional_generic_arg(its_value)->reify(); > > I'm very sympathetic to the desire to reduce repetition in argument > handling, but I'm not understanding what that means at all.  Partly > that's presentation; is this intended to be how the library would say > something, or how the caller would invoke the function? > > If the former, I don't understand what it means.  If the latter, are > you seriously suggesting this as a replacement for > > threaded_nut(required, args, here, optional_generic_arc=its_value); > I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). As a practical example, here is a invocation of a threaded module in my code: buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); here's how I would do it with the builder pattern and the suggested OO approach: buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); In this case, buttress_threaded_nut's implementation would change from 50 lines <https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/threading.scad#L1242> to 11: module buttress_threaded_nut(kwargs) {     profile = [         [  -1/2, -0.77],         [ -7/16, -0.75],         [  5/16,  0],         [  7/16,  0],         [  7/16, -0.75],         [  1/ 2, -0.77],     ];     generic_threaded_nut(struct(kwargs, profile=profile)); } Of course, this isn't OO and makes it harder to detect argument name typos and such. The builder pattern could be implemented as so: // namespace for buttress_threaded_nut_builder obj = struct(generic_threaded_nut_builder::new()); function new() =     let ( profile = [         [  -1/2, -0.77],         [ -7/16, -0.75],         [  5/16,  0],         [  7/16,  0],         [  7/16, -0.75],         [  1/ 2, -0.77],     ])     struct(obj)->profile(profile); so not any more or less difficult to write. What do profile and positioning look like? // namespace of generic_threaded_rod_builder function profile(o is builder_obj, profile) =     assert(is_list(profile)) // And other tests independent of other values     struct(o,profile=profile); // namespace of attachable_builder function positioning(o is attachable_builder_obj, anchor, spin, orient) =     assert(/* tests related to the parameters*/)     let(         l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],            is_undef(spin) ? [] : [["spin", spin]],            is_undef(orient) ? [] : [["orient", orient]])         )     struct(o,l); Okay, not in love with the repetition in there. But there are some improvements here: 1. Some validation can now stop cluttering the top of <https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/shapes3d.scad#L353> so many functions/modules. 2. The `get_radius` <https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/shapes3d.scad#L2461> function doesn't need its args filled out every time 3. We're reusing attachable instead of needing to redundantly pass it so many args every <https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/shapes3d.scad#L2469> single <https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/threading.scad#L2067> time <https://github.com/BelfrySCAD/BOSL2/blob/e940b69f554d499b3d24b09615d597ec59139e6b/walls.scad#L69> and in every function and module signature. 4. We can match on the old types and convert to objects as needed Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. Maybe attachable should be a mixin instead of in the class hierarchy. So how would I write this with `this` as currently proposed? function buttress_threaded_nut_builder =     let ( profile = [         [  -1/2, -0.77],         [ -7/16, -0.75],         [  5/16,  0],         [  7/16,  0],         [  7/16, -0.75],         [  1/ 2, -0.77],     ])     object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); // in method list of generic_threaded_rod_builder function set_profile(profile) =     assert(is_list(profile)) // And other tests independent of other values     object(this,profile=profile); // in method list of attachable_builder function set_positioning(anchor, spin, orient) =     assert(/* tests related to the parameters*/)     let(         l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],            is_undef(spin) ? [] : [["spin", spin]],            is_undef(orient) ? [] : [["orient", orient]])         )     struct(this,l); module reify_buttress_threaded_nut(obj) {     reify_threaded_nut(obj); } module reify_threaded_nut(obj) {     reify_generic_threaded_nut(obj); } reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); ... Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: bosl2_threading = object(     top_level_constant = 27,     function buttress_threaded_nut_builder() = ... ); module reify_buttress_threaded_nut(obj) {     bosl2_threading.top_level_constant; // for whatever reason     reify_threaded_nut(obj); } I've seen worse. I certainly can't say this rules out `this`.
PK
Peter Kriens
Tue, Aug 19, 2025 8:40 AM

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) I like the builder approach and it is one of my drivers for the object work and $this. BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... Peter Kriens > On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: > On 8/16/25 8:49 AM, Jordan Brown wrote: >>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >> threaded_nut(required, args, here, optional_generic_arc=its_value); > I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). > > As a practical example, here is a invocation of a threaded module in my code: > > buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); > > here's how I would do it with the builder pattern and the suggested OO approach: > > buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; > > You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: > > buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); > > In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: > > module buttress_threaded_nut(kwargs) { > profile = [ > [ -1/2, -0.77], > [ -7/16, -0.75], > [ 5/16, 0], > [ 7/16, 0], > [ 7/16, -0.75], > [ 1/ 2, -0.77], > ]; > generic_threaded_nut(struct(kwargs, profile=profile)); > } > > Of course, this isn't OO and makes it harder to detect argument name typos and such. > > The builder pattern could be implemented as so: > > // namespace for buttress_threaded_nut_builder > obj = struct(generic_threaded_nut_builder::new()); > function new() = > let ( profile = [ > [ -1/2, -0.77], > [ -7/16, -0.75], > [ 5/16, 0], > [ 7/16, 0], > [ 7/16, -0.75], > [ 1/ 2, -0.77], > ]) > struct(obj)->profile(profile); > > so not any more or less difficult to write. What do profile and positioning look like? > > // namespace of generic_threaded_rod_builder > function profile(o is builder_obj, profile) = > assert(is_list(profile)) // And other tests independent of other values > struct(o,profile=profile); > > // namespace of attachable_builder > function positioning(o is attachable_builder_obj, anchor, spin, orient) = > assert(/* tests related to the parameters*/) > let( > l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], > is_undef(spin) ? [] : [["spin", spin]], > is_undef(orient) ? [] : [["orient", orient]]) > ) > struct(o,l); > > Okay, not in love with the repetition in there. But there are some improvements here: > > 1. Some validation can now stop cluttering the top of so many functions/modules. > 2. The `get_radius` function doesn't need its args filled out every time > 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. > 4. We can match on the old types and convert to objects as needed > > Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. > Maybe attachable should be a mixin instead of in the class hierarchy. > > So how would I write this with `this` as currently proposed? > > function buttress_threaded_nut_builder = > let ( profile = [ > [ -1/2, -0.77], > [ -7/16, -0.75], > [ 5/16, 0], > [ 7/16, 0], > [ 7/16, -0.75], > [ 1/ 2, -0.77], > ]) > object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); > > // in method list of generic_threaded_rod_builder > function set_profile(profile) = > assert(is_list(profile)) // And other tests independent of other values > object(this,profile=profile); > > // in method list of attachable_builder > function set_positioning(anchor, spin, orient) = > assert(/* tests related to the parameters*/) > let( > l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], > is_undef(spin) ? [] : [["spin", spin]], > is_undef(orient) ? [] : [["orient", orient]]) > ) > struct(this,l); > > module reify_buttress_threaded_nut(obj) { > reify_threaded_nut(obj); > } > > > module reify_threaded_nut(obj) { > reify_generic_threaded_nut(obj); > } > > reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); > > > ... > > Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: > > bosl2_threading = object( > top_level_constant = 27, > function buttress_threaded_nut_builder() = > ... > ); > > module reify_buttress_threaded_nut(obj) { > bosl2_threading.top_level_constant; // for whatever reason > reify_threaded_nut(obj); > } > > I've seen worse. I certainly can't say this rules out `this`. > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
CC
Cory Cross
Wed, Aug 20, 2025 11:55 PM

Okay, I think I've identified the biggest problem with the proposed implementation of this: shadowed methods cannot be called on a child. Methods declared in a child can call a parent method on the parent object, but not with any values that may be defined or updated by the child constructor.

I also dislike that you must define all methods in one context and file location. What I've proposed gives the users the same power as the library writers, while this method locks ownership of objects to the original author. (Obviously, yes, we can all edit any code, compiled or otherwise, on our machines, but it's not afforded by the language and it's a lot harder to ship patches to someone else's library than to ship your own file that adds a method to a namespace).

As far as future optimization, because of the dynamic context, can this approach be optimized well or is it like Python and doomed to always be slow? Well, lua is ultra flexible and luajit was apparently pretty amazing. So it might not be impossible.

I like the builder approach and it is one of my drivers for the object work and $this.

Well... as I think about it further, we don't even need objects at all to do the builder pattern, since, as implemented, they're equivalent in power to a plist, you can do everything proposed with lists, concat, a plist lookup and a plist call functions:

function lookup(plist, pname, idx=0) =
let ( kv = plist[idx] ) is_undef(kv) ? kv : kv[0] == pname ? kv[1] : lookup(plist, pname, idx+1);

function call(obj, fname, args_plist) =
let( f = lookup(obj, fname) ) is_function(f) ? f(obj, args_plist) : undef;

function any_method(obj, args_plist) =
let( actual_arg = lookup(args_plist, "actual_arg"),
member = lookup(obj, "member"),
) implementation;

x=call(myobj, "distance", [["p",other_point]]) // call method distance on my object to calculate the distance from other_point
x=myobj.distance(p=other_point); // doesn't look so different, does it?

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();
I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);
I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Okay, I think I've identified the biggest problem with the proposed implementation of this: shadowed methods cannot be called on a child. Methods declared in a child can call a parent method on the parent object, but not with any values that may be defined or updated by the child constructor. I also dislike that you must define all methods in one context and file location. What I've proposed gives the users the same power as the library writers, while this method locks ownership of objects to the original author. (Obviously, yes, we can all edit any code, compiled or otherwise, on our machines, but it's not afforded by the language and it's a lot harder to ship patches to someone else's library than to ship your own file that adds a method to a namespace). As far as future optimization, because of the dynamic context, can this approach be optimized well or is it like Python and doomed to always be slow? Well, lua is ultra flexible and luajit was apparently pretty amazing. So it might not be impossible. > I like the builder approach and it is one of my drivers for the object work and $this. Well... as I think about it further, we don't even need objects at all to do the builder pattern, since, as implemented, they're equivalent in power to a plist, you can do everything proposed with lists, concat, a plist lookup and a plist call functions: function lookup(plist, pname, idx=0) = let ( kv = plist[idx] ) is_undef(kv) ? kv : kv[0] == pname ? kv[1] : lookup(plist, pname, idx+1); function call(obj, fname, args_plist) = let( f = lookup(obj, fname) ) is_function(f) ? f(obj, args_plist) : undef; function any_method(obj, args_plist) = let( actual_arg = lookup(args_plist, "actual_arg"), member = lookup(obj, "member"), ) implementation; x=call(myobj, "distance", [["p",other_point]]) // call method distance on my object to calculate the distance from other_point x=myobj.distance(p=other_point); // doesn't look so different, does it? So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? Actually, plists are better because you can get super methods :-) I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) > >I like the builder approach and it is one of my drivers for the object work and $this. > >BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... > > Peter Kriens > >> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>> threaded_nut(required, args, here, optional_generic_arc=its_value); >> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >> >> As a practical example, here is a invocation of a threaded module in my code: >> >> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >> >> here's how I would do it with the builder pattern and the suggested OO approach: >> >> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >> >> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >> >> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >> >> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >> >> module buttress_threaded_nut(kwargs) { >> profile = [ >> [ -1/2, -0.77], >> [ -7/16, -0.75], >> [ 5/16, 0], >> [ 7/16, 0], >> [ 7/16, -0.75], >> [ 1/ 2, -0.77], >> ]; >> generic_threaded_nut(struct(kwargs, profile=profile)); >> } >> >> Of course, this isn't OO and makes it harder to detect argument name typos and such. >> >> The builder pattern could be implemented as so: >> >> // namespace for buttress_threaded_nut_builder >> obj = struct(generic_threaded_nut_builder::new()); >> function new() = >> let ( profile = [ >> [ -1/2, -0.77], >> [ -7/16, -0.75], >> [ 5/16, 0], >> [ 7/16, 0], >> [ 7/16, -0.75], >> [ 1/ 2, -0.77], >> ]) >> struct(obj)->profile(profile); >> >> so not any more or less difficult to write. What do profile and positioning look like? >> >> // namespace of generic_threaded_rod_builder >> function profile(o is builder_obj, profile) = >> assert(is_list(profile)) // And other tests independent of other values >> struct(o,profile=profile); >> >> // namespace of attachable_builder >> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >> assert(/* tests related to the parameters*/) >> let( >> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >> is_undef(spin) ? [] : [["spin", spin]], >> is_undef(orient) ? [] : [["orient", orient]]) >> ) >> struct(o,l); >> >> Okay, not in love with the repetition in there. But there are some improvements here: >> >> 1. Some validation can now stop cluttering the top of so many functions/modules. >> 2. The `get_radius` function doesn't need its args filled out every time >> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >> 4. We can match on the old types and convert to objects as needed >> >> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >> Maybe attachable should be a mixin instead of in the class hierarchy. >> >> So how would I write this with `this` as currently proposed? >> >> function buttress_threaded_nut_builder = >> let ( profile = [ >> [ -1/2, -0.77], >> [ -7/16, -0.75], >> [ 5/16, 0], >> [ 7/16, 0], >> [ 7/16, -0.75], >> [ 1/ 2, -0.77], >> ]) >> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >> >> // in method list of generic_threaded_rod_builder >> function set_profile(profile) = >> assert(is_list(profile)) // And other tests independent of other values >> object(this,profile=profile); >> >> // in method list of attachable_builder >> function set_positioning(anchor, spin, orient) = >> assert(/* tests related to the parameters*/) >> let( >> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >> is_undef(spin) ? [] : [["spin", spin]], >> is_undef(orient) ? [] : [["orient", orient]]) >> ) >> struct(this,l); >> >> module reify_buttress_threaded_nut(obj) { >> reify_threaded_nut(obj); >> } >> >> >> module reify_threaded_nut(obj) { >> reify_generic_threaded_nut(obj); >> } >> >> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >> >> >> ... >> >> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >> >> bosl2_threading = object( >> top_level_constant = 27, >> function buttress_threaded_nut_builder() = >> ... >> ); >> >> module reify_buttress_threaded_nut(obj) { >> bosl2_threading.top_level_constant; // for whatever reason >> reify_threaded_nut(obj); >> } >> >> I've seen worse. I certainly can't say this rules out `this`. >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org > >_______________________________________________ >OpenSCAD mailing list >To unsubscribe send an email to discuss-leave@lists.openscad.org
CC
Cory Cross
Thu, Aug 21, 2025 6:01 AM

Well... as I think about it further, we don't even need objects at all to do the builder pattern, since, as implemented, they're equivalent in power to a plist, you can do everything proposed with lists, concat, a plist lookup and a plist call functions:

Bah, these are alists, of course, and not plists. Either could be used though.

>Well... as I think about it further, we don't even need objects at all to do the builder pattern, since, as implemented, they're equivalent in power to a plist, you can do everything proposed with lists, concat, a plist lookup and a plist call functions: Bah, these are alists, of course, and not plists. Either could be used though.
PK
Peter Kriens
Thu, Aug 21, 2025 1:57 PM

On 21 Aug 2025, at 01:55, Cory Cross via Discuss discuss@lists.openscad.org wrote:

Okay, I think I've identified the biggest problem with the proposed implementation of this: shadowed methods cannot be called on a child. Methods declared in a child can call a parent method on the parent object, but not with any values that may be defined or updated by the child constructor.

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

I also dislike that you must define all methods in one context and file location. What I've proposed gives the users the same power as the library writers, while this method locks ownership of objects to the original author. (Obviously, yes, we can all edit any code, compiled or otherwise, on our machines, but it's not afforded by the language and it's a lot harder to ship patches to someone else's library than to ship your own file that adds a method to a namespace).

As far as future optimization, because of the dynamic context, can this approach be optimized well or is it like Python and doomed to always be slow? Well, lua is ultra flexible and luajit was apparently pretty amazing. So it might not be impossible.

I like the builder approach and it is one of my drivers for the object work and $this.

Well... as I think about it further, we don't even need objects at all to do the builder pattern, since, as implemented, they're equivalent in power to a plist, you can do everything proposed with lists, concat, a plist lookup and a plist call functions:

function lookup(plist, pname, idx=0) =
let ( kv = plist[idx] ) is_undef(kv) ? kv : kv[0] == pname ? kv[1] : lookup(plist, pname, idx+1);

function call(obj, fname, args_plist) =
let( f = lookup(obj, fname) ) is_function(f) ? f(obj, args_plist) : undef;

function any_method(obj, args_plist) =
let( actual_arg = lookup(args_plist, "actual_arg"),
member = lookup(obj, "member"),
) implementation;

x=call(myobj, "distance", [["p",other_point]]) // call method distance on my object to calculate the distance from other_point
x=myobj.distance(p=other_point); // doesn't look so different, does it?

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

This PR added methods to object(). Torsten then raised the issue how callbacks should be handled. There are various way to handle this but that raised the question what should be the default: method bound to its object by default or through some keyword/operator/function. It was that simple.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project. However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: > > Okay, I think I've identified the biggest problem with the proposed implementation of this: shadowed methods cannot be called on a child. Methods declared in a child can call a parent method on the parent object, but not with any values that may be defined or updated by the child constructor. Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. > > I also dislike that you must define all methods in one context and file location. What I've proposed gives the users the same power as the library writers, while this method locks ownership of objects to the original author. (Obviously, yes, we can all edit any code, compiled or otherwise, on our machines, but it's not afforded by the language and it's a lot harder to ship patches to someone else's library than to ship your own file that adds a method to a namespace). > > As far as future optimization, because of the dynamic context, can this approach be optimized well or is it like Python and doomed to always be slow? Well, lua is ultra flexible and luajit was apparently pretty amazing. So it might not be impossible. > >> I like the builder approach and it is one of my drivers for the object work and $this. > > Well... as I think about it further, we don't even need objects at all to do the builder pattern, since, as implemented, they're equivalent in power to a plist, you can do everything proposed with lists, concat, a plist lookup and a plist call functions: > > function lookup(plist, pname, idx=0) = > let ( kv = plist[idx] ) is_undef(kv) ? kv : kv[0] == pname ? kv[1] : lookup(plist, pname, idx+1); > > function call(obj, fname, args_plist) = > let( f = lookup(obj, fname) ) is_function(f) ? f(obj, args_plist) : undef; > > function any_method(obj, args_plist) = > let( actual_arg = lookup(args_plist, "actual_arg"), > member = lookup(obj, "member"), > ) implementation; > > x=call(myobj, "distance", [["p",other_point]]) // call method distance on my object to calculate the distance from other_point > x=myobj.distance(p=other_point); // doesn't look so different, does it? > > So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. This PR added methods to object(). Torsten then raised the issue how callbacks should be handled. There are various way to handle this but that raised the question what should be the default: method bound to its object by default or through some keyword/operator/function. It was that simple. Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... I am new here so I might not understand the mores in this project. However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) Peter > > Actually, plists are better because you can get super methods :-) > > > I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. > > > > On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >> >> I like the builder approach and it is one of my drivers for the object work and $this. >> >> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >> >> Peter Kriens >> >>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>> >>> As a practical example, here is a invocation of a threaded module in my code: >>> >>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>> >>> here's how I would do it with the builder pattern and the suggested OO approach: >>> >>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>> >>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>> >>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>> >>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>> >>> module buttress_threaded_nut(kwargs) { >>> profile = [ >>> [ -1/2, -0.77], >>> [ -7/16, -0.75], >>> [ 5/16, 0], >>> [ 7/16, 0], >>> [ 7/16, -0.75], >>> [ 1/ 2, -0.77], >>> ]; >>> generic_threaded_nut(struct(kwargs, profile=profile)); >>> } >>> >>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>> >>> The builder pattern could be implemented as so: >>> >>> // namespace for buttress_threaded_nut_builder >>> obj = struct(generic_threaded_nut_builder::new()); >>> function new() = >>> let ( profile = [ >>> [ -1/2, -0.77], >>> [ -7/16, -0.75], >>> [ 5/16, 0], >>> [ 7/16, 0], >>> [ 7/16, -0.75], >>> [ 1/ 2, -0.77], >>> ]) >>> struct(obj)->profile(profile); >>> >>> so not any more or less difficult to write. What do profile and positioning look like? >>> >>> // namespace of generic_threaded_rod_builder >>> function profile(o is builder_obj, profile) = >>> assert(is_list(profile)) // And other tests independent of other values >>> struct(o,profile=profile); >>> >>> // namespace of attachable_builder >>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>> assert(/* tests related to the parameters*/) >>> let( >>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>> is_undef(spin) ? [] : [["spin", spin]], >>> is_undef(orient) ? [] : [["orient", orient]]) >>> ) >>> struct(o,l); >>> >>> Okay, not in love with the repetition in there. But there are some improvements here: >>> >>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>> 2. The `get_radius` function doesn't need its args filled out every time >>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>> 4. We can match on the old types and convert to objects as needed >>> >>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>> Maybe attachable should be a mixin instead of in the class hierarchy. >>> >>> So how would I write this with `this` as currently proposed? >>> >>> function buttress_threaded_nut_builder = >>> let ( profile = [ >>> [ -1/2, -0.77], >>> [ -7/16, -0.75], >>> [ 5/16, 0], >>> [ 7/16, 0], >>> [ 7/16, -0.75], >>> [ 1/ 2, -0.77], >>> ]) >>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>> >>> // in method list of generic_threaded_rod_builder >>> function set_profile(profile) = >>> assert(is_list(profile)) // And other tests independent of other values >>> object(this,profile=profile); >>> >>> // in method list of attachable_builder >>> function set_positioning(anchor, spin, orient) = >>> assert(/* tests related to the parameters*/) >>> let( >>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>> is_undef(spin) ? [] : [["spin", spin]], >>> is_undef(orient) ? [] : [["orient", orient]]) >>> ) >>> struct(this,l); >>> >>> module reify_buttress_threaded_nut(obj) { >>> reify_threaded_nut(obj); >>> } >>> >>> >>> module reify_threaded_nut(obj) { >>> reify_generic_threaded_nut(obj); >>> } >>> >>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>> >>> >>> ... >>> >>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>> >>> bosl2_threading = object( >>> top_level_constant = 27, >>> function buttress_threaded_nut_builder() = >>> ... >>> ); >>> >>> module reify_buttress_threaded_nut(obj) { >>> bosl2_threading.top_level_constant; // for whatever reason >>> reify_threaded_nut(obj); >>> } >>> >>> I've seen worse. I certainly can't say this rules out `this`. >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org >> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
CC
Cory Cross
Thu, Aug 21, 2025 9:18 PM

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss discuss@lists.openscad.org wrote:

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross
Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();
I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);
I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. > We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. Following this logic, not adding it is simpler. What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. >> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? > >Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. >Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... > >I am new here so I might not understand the mores in this project. I am new as well (though a user for many years). > However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. >The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. I also think it's important to keep momentum up. >I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) I can be a bit blunt but you can blame it on my Dutch ancestry :-). - Cory Cross > > Peter > > > >> >> Actually, plists are better because you can get super methods :-) >> >> >> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >> >> >> >> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>> >>> I like the builder approach and it is one of my drivers for the object work and $this. >>> >>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>> >>> Peter Kriens >>> >>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>> >>>> As a practical example, here is a invocation of a threaded module in my code: >>>> >>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>> >>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>> >>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>> >>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>> >>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>> >>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>> >>>> module buttress_threaded_nut(kwargs) { >>>> profile = [ >>>> [ -1/2, -0.77], >>>> [ -7/16, -0.75], >>>> [ 5/16, 0], >>>> [ 7/16, 0], >>>> [ 7/16, -0.75], >>>> [ 1/ 2, -0.77], >>>> ]; >>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>> } >>>> >>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>> >>>> The builder pattern could be implemented as so: >>>> >>>> // namespace for buttress_threaded_nut_builder >>>> obj = struct(generic_threaded_nut_builder::new()); >>>> function new() = >>>> let ( profile = [ >>>> [ -1/2, -0.77], >>>> [ -7/16, -0.75], >>>> [ 5/16, 0], >>>> [ 7/16, 0], >>>> [ 7/16, -0.75], >>>> [ 1/ 2, -0.77], >>>> ]) >>>> struct(obj)->profile(profile); >>>> >>>> so not any more or less difficult to write. What do profile and positioning look like? >>>> >>>> // namespace of generic_threaded_rod_builder >>>> function profile(o is builder_obj, profile) = >>>> assert(is_list(profile)) // And other tests independent of other values >>>> struct(o,profile=profile); >>>> >>>> // namespace of attachable_builder >>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>> assert(/* tests related to the parameters*/) >>>> let( >>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>> is_undef(spin) ? [] : [["spin", spin]], >>>> is_undef(orient) ? [] : [["orient", orient]]) >>>> ) >>>> struct(o,l); >>>> >>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>> >>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>> 2. The `get_radius` function doesn't need its args filled out every time >>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>> 4. We can match on the old types and convert to objects as needed >>>> >>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>> >>>> So how would I write this with `this` as currently proposed? >>>> >>>> function buttress_threaded_nut_builder = >>>> let ( profile = [ >>>> [ -1/2, -0.77], >>>> [ -7/16, -0.75], >>>> [ 5/16, 0], >>>> [ 7/16, 0], >>>> [ 7/16, -0.75], >>>> [ 1/ 2, -0.77], >>>> ]) >>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>> >>>> // in method list of generic_threaded_rod_builder >>>> function set_profile(profile) = >>>> assert(is_list(profile)) // And other tests independent of other values >>>> object(this,profile=profile); >>>> >>>> // in method list of attachable_builder >>>> function set_positioning(anchor, spin, orient) = >>>> assert(/* tests related to the parameters*/) >>>> let( >>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>> is_undef(spin) ? [] : [["spin", spin]], >>>> is_undef(orient) ? [] : [["orient", orient]]) >>>> ) >>>> struct(this,l); >>>> >>>> module reify_buttress_threaded_nut(obj) { >>>> reify_threaded_nut(obj); >>>> } >>>> >>>> >>>> module reify_threaded_nut(obj) { >>>> reify_generic_threaded_nut(obj); >>>> } >>>> >>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>> >>>> >>>> ... >>>> >>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>> >>>> bosl2_threading = object( >>>> top_level_constant = 27, >>>> function buttress_threaded_nut_builder() = >>>> ... >>>> ); >>>> >>>> module reify_buttress_threaded_nut(obj) { >>>> bosl2_threading.top_level_constant; // for whatever reason >>>> reify_threaded_nut(obj); >>>> } >>>> >>>> I've seen worse. I certainly can't say this rules out `this`. >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>> >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org >_______________________________________________ >OpenSCAD mailing list >To unsubscribe send an email to discuss-leave@lists.openscad.org
JB
Jon Bondy
Thu, Aug 21, 2025 9:56 PM

I have to agree with this.  If you call it an "object" then it should
conform to most people's intuitions about that facility. If you want to
create something that is very different than an object, that is fine;
just don't call it an object.

Jon

On 8/21/2025 5:18 PM, Cory Cross via Discuss wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss discuss@lists.openscad.org wrote:

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

--
This email has been checked for viruses by AVG antivirus software.
www.avg.com

I have to agree with this.  If you call it an "object" then it should conform to most people's intuitions about that facility. If you want to create something that is very different than an object, that is fine; just don't call it an object. Jon On 8/21/2025 5:18 PM, Cory Cross via Discuss wrote: > On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. > Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. > -- This email has been checked for viruses by AVG antivirus software. www.avg.com
LM
Leonard Martin Struttmann
Thu, Aug 21, 2025 11:28 PM

I very much agree with Jon's sentiment.  When I first heard that the new
things were going to be called "objects" I immediately, erroneously,
thought of Object Oriented programming.  I was relieved to learn we were
talking about the nearly equivalent of a Python dictionary.  Calling these
things "objects" will cause so much confusion.

Len

On Thu, Aug 21, 2025 at 4:56 PM Jon Bondy via Discuss <
discuss@lists.openscad.org> wrote:

I have to agree with this.  If you call it an "object" then it should
conform to most people's intuitions about that facility. If you want to
create something that is very different than an object, that is fine;
just don't call it an object.

Jon

On 8/21/2025 5:18 PM, Cory Cross via Discuss wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <

Objects do NOT have parents so I am not sure what you're talking about?

An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have

or will use OpenSCAD will associate objects with the mainstream
object-oriented languages which all put inheritance front-and-center. It's
day-one "learning Python" material. I don't think there's a single language
with a "this" keyword that doesn't have inheritance. You're setting up
people for confusion.

--
This email has been checked for viruses by AVG antivirus software.
www.avg.com


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

I very much agree with Jon's sentiment. When I first heard that the new things were going to be called "objects" I immediately, erroneously, thought of Object Oriented programming. I was relieved to learn we were talking about the nearly equivalent of a Python dictionary. Calling these things "objects" will cause so much confusion. Len On Thu, Aug 21, 2025 at 4:56 PM Jon Bondy via Discuss < discuss@lists.openscad.org> wrote: > I have to agree with this. If you call it an "object" then it should > conform to most people's intuitions about that facility. If you want to > create something that is very different than an object, that is fine; > just don't call it an object. > > Jon > > On 8/21/2025 5:18 PM, Cory Cross via Discuss wrote: > > On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss < > discuss@lists.openscad.org> wrote: > >>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss < > discuss@lists.openscad.org> wrote: > >> Objects do NOT have parents so I am not sure what you're talking about? > An object is a flat set of key-value pairs. There is no hierarchy. > > Then they shouldn't be called objects. At least 99% of people who have > or will use OpenSCAD will associate objects with the mainstream > object-oriented languages which all put inheritance front-and-center. It's > day-one "learning Python" material. I don't think there's a single language > with a "this" keyword that doesn't have inheritance. You're setting up > people for confusion. > > > > -- > This email has been checked for viruses by AVG antivirus software. > www.avg.com > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
FH
Father Horton
Thu, Aug 21, 2025 11:39 PM

FWIW, I also think that calling the new construct an “object” is more
confusing than helpful.

FWIW, I also think that calling the new construct an “object” is more confusing than helpful.
NS
Nathan Sokalski
Fri, Aug 22, 2025 12:16 AM

I agree that using the term object when certain attributes are missing/different could be confusing if someone is learning OpenSCAD for the first time, but I think it could also be confusing to try to use an unconventional or non-standard term. When learning a new language that is not based on one I have previously used (in other words, one with different key words), the hardest part for me is usually becoming familiar with the new key words. Even though this would still be OpenSCAD (not a completely new language), learning another key word rather than one that developers and other tech people that use OpenSCAD are familiar with is not worth it. The term object is intuitive enough that for a simple, not full-fledged scripting language it is worth the sacrifice of not meeting the technical definition of an object. OpenSCAD does not do complex communication with other languages, so it's OK to cut a few corners as long we understand it easily.

Nathan Sokalski
njsokalski@hotmail.commailto:njsokalski@hotmail.com


From: Father Horton via Discuss discuss@lists.openscad.org
Sent: Thursday, August 21, 2025 7:39 PM
To: OpenSCAD general discussion Mailing-list discuss@lists.openscad.org
Cc: Father Horton fatherhorton@gmail.com
Subject: [OpenSCAD] Re: Future of OO in OpenSCAD (was Re: Re: New feature in 2025.07.11: the object() function)

FWIW, I also think that calling the new construct an “object” is more confusing than helpful.

I agree that using the term object when certain attributes are missing/different could be confusing if someone is learning OpenSCAD for the first time, but I think it could also be confusing to try to use an unconventional or non-standard term. When learning a new language that is not based on one I have previously used (in other words, one with different key words), the hardest part for me is usually becoming familiar with the new key words. Even though this would still be OpenSCAD (not a completely new language), learning another key word rather than one that developers and other tech people that use OpenSCAD are familiar with is not worth it. The term object is intuitive enough that for a simple, not full-fledged scripting language it is worth the sacrifice of not meeting the technical definition of an object. OpenSCAD does not do complex communication with other languages, so it's OK to cut a few corners as long we understand it easily. Nathan Sokalski njsokalski@hotmail.com<mailto:njsokalski@hotmail.com> ________________________________ From: Father Horton via Discuss <discuss@lists.openscad.org> Sent: Thursday, August 21, 2025 7:39 PM To: OpenSCAD general discussion Mailing-list <discuss@lists.openscad.org> Cc: Father Horton <fatherhorton@gmail.com> Subject: [OpenSCAD] Re: Future of OO in OpenSCAD (was Re: Re: New feature in 2025.07.11: the object() function) FWIW, I also think that calling the new construct an “object” is more confusing than helpful.
HW
Harvey white
Fri, Aug 22, 2025 4:07 AM

You might consider calling the "this" a "thing" which may help with
understanding a bit.

Therefore instead of "this->" use "thing->"

Harvey

On 8/21/2025 7:39 PM, Father Horton via Discuss wrote:

FWIW, I also think that calling the new construct an “object” is more
confusing than helpful.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

You might consider calling the "this" a "thing" which may help with understanding a bit. Therefore instead of "this->" use "thing->" Harvey On 8/21/2025 7:39 PM, Father Horton via Discuss wrote: > FWIW, I also think that calling the new construct an “object” is more > confusing than helpful. > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
MK
Marius Kintel
Fri, Aug 22, 2025 1:20 PM

I guess nomenclature will always be confusing to some people depending on their background. I think about OpenSCAD objects in a similar way as I do JSON objects, POJOs, or even JavaScript objects. The surrounding runtime system may or may not use it for more than storing key-value pairs, but storing key-value pairs is still a core feature.

On Aug 22, 2025, at 00:07, Harvey white via Discuss discuss@lists.openscad.org wrote:

You might consider calling the "this" a "thing" which may help with understanding a bit.

Therefore instead of "this->" use "thing->"

Harvey

On 8/21/2025 7:39 PM, Father Horton via Discuss wrote:
FWIW, I also think that calling the new construct an “object” is more confusing than helpful.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

I guess nomenclature will always be confusing to some people depending on their background. I think about OpenSCAD objects in a similar way as I do JSON objects, POJOs, or even JavaScript objects. The surrounding runtime system may or may not use it for more than storing key-value pairs, but storing key-value pairs is still a core feature. > On Aug 22, 2025, at 00:07, Harvey white via Discuss <discuss@lists.openscad.org> wrote: > > You might consider calling the "this" a "thing" which may help with understanding a bit. > > Therefore instead of "this->" use "thing->" > > Harvey > > >> On 8/21/2025 7:39 PM, Father Horton via Discuss wrote: >> FWIW, I also think that calling the new construct an “object” is more confusing than helpful. >> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
PK
Peter Kriens
Fri, Aug 22, 2025 2:14 PM

This discussion is imho a perfect example of bikeshedding https://en.wiktionary.org/wiki/bikeshedding. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs ... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies.

I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours.

I personally would appreciate a time box so we can make sure this does not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross
Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org

This discussion is imho a perfect example of bikeshedding <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs ... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies. I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours. I personally would appreciate a time box so we can make sure this does not get delayed another two years again. Peter > On 21 Aug 2025, at 23:18, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: > > > > On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. > > Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. > >> We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. > > Following this logic, not adding it is simpler. > > What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. > >>> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? >> >> Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. > > "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. > >> Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... >> >> I am new here so I might not understand the mores in this project. > > I am new as well (though a user for many years). > >> However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... > > I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. > > It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. > > I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? > > Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. > >> The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. > > I also think it's important to keep momentum up. > >> I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) > > I can be a bit blunt but you can blame it on my Dutch ancestry :-). > > - Cory Cross > >> >> Peter >> >> >> >>> >>> Actually, plists are better because you can get super methods :-) >>> >>> >>> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >>> >>> >>> >>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>>> >>>> I like the builder approach and it is one of my drivers for the object work and $this. >>>> >>>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>>> >>>> Peter Kriens >>>> >>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>>> >>>>> As a practical example, here is a invocation of a threaded module in my code: >>>>> >>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>> >>>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>>> >>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>> >>>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>>> >>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>> >>>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>>> >>>>> module buttress_threaded_nut(kwargs) { >>>>> profile = [ >>>>> [ -1/2, -0.77], >>>>> [ -7/16, -0.75], >>>>> [ 5/16, 0], >>>>> [ 7/16, 0], >>>>> [ 7/16, -0.75], >>>>> [ 1/ 2, -0.77], >>>>> ]; >>>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>>> } >>>>> >>>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>>> >>>>> The builder pattern could be implemented as so: >>>>> >>>>> // namespace for buttress_threaded_nut_builder >>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>> function new() = >>>>> let ( profile = [ >>>>> [ -1/2, -0.77], >>>>> [ -7/16, -0.75], >>>>> [ 5/16, 0], >>>>> [ 7/16, 0], >>>>> [ 7/16, -0.75], >>>>> [ 1/ 2, -0.77], >>>>> ]) >>>>> struct(obj)->profile(profile); >>>>> >>>>> so not any more or less difficult to write. What do profile and positioning look like? >>>>> >>>>> // namespace of generic_threaded_rod_builder >>>>> function profile(o is builder_obj, profile) = >>>>> assert(is_list(profile)) // And other tests independent of other values >>>>> struct(o,profile=profile); >>>>> >>>>> // namespace of attachable_builder >>>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>>> assert(/* tests related to the parameters*/) >>>>> let( >>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>> ) >>>>> struct(o,l); >>>>> >>>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>>> >>>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>>> 2. The `get_radius` function doesn't need its args filled out every time >>>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>>> 4. We can match on the old types and convert to objects as needed >>>>> >>>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>> >>>>> So how would I write this with `this` as currently proposed? >>>>> >>>>> function buttress_threaded_nut_builder = >>>>> let ( profile = [ >>>>> [ -1/2, -0.77], >>>>> [ -7/16, -0.75], >>>>> [ 5/16, 0], >>>>> [ 7/16, 0], >>>>> [ 7/16, -0.75], >>>>> [ 1/ 2, -0.77], >>>>> ]) >>>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>>> >>>>> // in method list of generic_threaded_rod_builder >>>>> function set_profile(profile) = >>>>> assert(is_list(profile)) // And other tests independent of other values >>>>> object(this,profile=profile); >>>>> >>>>> // in method list of attachable_builder >>>>> function set_positioning(anchor, spin, orient) = >>>>> assert(/* tests related to the parameters*/) >>>>> let( >>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>> ) >>>>> struct(this,l); >>>>> >>>>> module reify_buttress_threaded_nut(obj) { >>>>> reify_threaded_nut(obj); >>>>> } >>>>> >>>>> >>>>> module reify_threaded_nut(obj) { >>>>> reify_generic_threaded_nut(obj); >>>>> } >>>>> >>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>> >>>>> >>>>> ... >>>>> >>>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>>> >>>>> bosl2_threading = object( >>>>> top_level_constant = 27, >>>>> function buttress_threaded_nut_builder() = >>>>> ... >>>>> ); >>>>> >>>>> module reify_buttress_threaded_nut(obj) { >>>>> bosl2_threading.top_level_constant; // for whatever reason >>>>> reify_threaded_nut(obj); >>>>> } >>>>> >>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org>
CC
Cory Cross
Fri, Aug 22, 2025 4:05 PM

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

This discussion is imho a perfect example of bikeshedding https://en.wiktionary.org/wiki/bikeshedding. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs

Let's stop trying to make them objects and just call them dictionary, map, associative array, or any of the many other names for this: https://en.m.wikipedia.org/wiki/Associative_array

The words you use carry connotations and have denotations that users will use to understand what it is. If you offer to pay 100 Euros and show up with a 1-Euro bill, the other party is going to be upset and confused even if you explain you don't pronounce the "ents" of "cents".

  • Cory

I personally would appreciate a time box so we can make sure this does not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:
Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross

Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();
I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);
I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >This discussion is imho a perfect example of bikeshedding <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs Let's stop trying to make them objects and just call them dictionary, map, associative array, or any of the many other names for this: https://en.m.wikipedia.org/wiki/Associative_array The words you use carry connotations and have denotations that users will use to understand what it is. If you offer to pay 100 Euros and show up with a 1-Euro bill, the other party is going to be upset and confused even if you explain you don't pronounce the "ents" of "cents". - Cory >I personally would appreciate a time box so we can make sure this does not get delayed another two years again. > > Peter > > >> On 21 Aug 2025, at 23:18, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >> >> >> >> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. >> >> Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. >> >>> We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. >> >> Following this logic, not adding it is simpler. >> >> What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. >> >>>> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? >>> >>> Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. >> >> "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. >> >>> Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... >>> >>> I am new here so I might not understand the mores in this project. >> >> I am new as well (though a user for many years). >> >>> However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... >> >> I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. >> >> It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. >> >> I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? >> >> Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. >> >>> The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. >> >> I also think it's important to keep momentum up. >> >>> I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) >> >> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >> >> - Cory Cross >> >>> >>> Peter >>> >>> >>> >>>> >>>> Actually, plists are better because you can get super methods :-) >>>> >>>> >>>> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >>>> >>>> >>>> >>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>>>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>>>> >>>>> I like the builder approach and it is one of my drivers for the object work and $this. >>>>> >>>>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>>>> >>>>> Peter Kriens >>>>> >>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>>>> >>>>>> As a practical example, here is a invocation of a threaded module in my code: >>>>>> >>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>> >>>>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>>>> >>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>> >>>>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>>>> >>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>> >>>>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>>>> >>>>>> module buttress_threaded_nut(kwargs) { >>>>>> profile = [ >>>>>> [ -1/2, -0.77], >>>>>> [ -7/16, -0.75], >>>>>> [ 5/16, 0], >>>>>> [ 7/16, 0], >>>>>> [ 7/16, -0.75], >>>>>> [ 1/ 2, -0.77], >>>>>> ]; >>>>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>> } >>>>>> >>>>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>>>> >>>>>> The builder pattern could be implemented as so: >>>>>> >>>>>> // namespace for buttress_threaded_nut_builder >>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>> function new() = >>>>>> let ( profile = [ >>>>>> [ -1/2, -0.77], >>>>>> [ -7/16, -0.75], >>>>>> [ 5/16, 0], >>>>>> [ 7/16, 0], >>>>>> [ 7/16, -0.75], >>>>>> [ 1/ 2, -0.77], >>>>>> ]) >>>>>> struct(obj)->profile(profile); >>>>>> >>>>>> so not any more or less difficult to write. What do profile and positioning look like? >>>>>> >>>>>> // namespace of generic_threaded_rod_builder >>>>>> function profile(o is builder_obj, profile) = >>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>> struct(o,profile=profile); >>>>>> >>>>>> // namespace of attachable_builder >>>>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>>>> assert(/* tests related to the parameters*/) >>>>>> let( >>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>> ) >>>>>> struct(o,l); >>>>>> >>>>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>>>> >>>>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>>>> 2. The `get_radius` function doesn't need its args filled out every time >>>>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>>>> 4. We can match on the old types and convert to objects as needed >>>>>> >>>>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>>> >>>>>> So how would I write this with `this` as currently proposed? >>>>>> >>>>>> function buttress_threaded_nut_builder = >>>>>> let ( profile = [ >>>>>> [ -1/2, -0.77], >>>>>> [ -7/16, -0.75], >>>>>> [ 5/16, 0], >>>>>> [ 7/16, 0], >>>>>> [ 7/16, -0.75], >>>>>> [ 1/ 2, -0.77], >>>>>> ]) >>>>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>>>> >>>>>> // in method list of generic_threaded_rod_builder >>>>>> function set_profile(profile) = >>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>> object(this,profile=profile); >>>>>> >>>>>> // in method list of attachable_builder >>>>>> function set_positioning(anchor, spin, orient) = >>>>>> assert(/* tests related to the parameters*/) >>>>>> let( >>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>> ) >>>>>> struct(this,l); >>>>>> >>>>>> module reify_buttress_threaded_nut(obj) { >>>>>> reify_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> >>>>>> module reify_threaded_nut(obj) { >>>>>> reify_generic_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>> >>>>>> >>>>>> ... >>>>>> >>>>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>>>> >>>>>> bosl2_threading = object( >>>>>> top_level_constant = 27, >>>>>> function buttress_threaded_nut_builder() = >>>>>> ... >>>>>> ); >>>>>> >>>>>> module reify_buttress_threaded_nut(obj) { >>>>>> bosl2_threading.top_level_constant; // for whatever reason >>>>>> reify_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>> _______________________________________________ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org>
GB
Glenn Butcher
Fri, Aug 22, 2025 4:09 PM

After learning and using three different object-oriented languages, I
found they all behave a little differently with respect to "OO
principles". If OpenSCAD's objects are yet again different, no problem,
I just go with the flow.

The key thing here for me is, "do OpenSCAD objects let me do something
that facilitates my modeling?"  If 'yes', then I'll figure out how it
works, however it works.  Right now, I'm not seeing such, but that's
okay, I'm just one use-case.

Glenn

On 8/22/2025 8:14 AM, Peter Kriens via Discuss wrote:

This discussion is imho a perfect example of bikeshedding
https://en.wiktionary.org/wiki/bikeshedding. We can discuss this
forever on a forum like this because we're all highly experienced &
opinionated but in the mean time OpenSCAD has no KV pairs ... The
enemy of good is 'better' ... This is exactly how the OEP8 effort got
derailed more than 2 years ago. At a certain moment everybody gets
confused and it dies.

I suggest you make a working PR so we can play with concrete proposals
instead of trying to infer what your sketches mean. If I can use KV
pairs and get some concise way to make methods I happily close my PR
and support yours.

I personally would appreciate a time box so we can make sure this does
not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss
discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss
discuss@lists.openscad.org wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss
discuss@lists.openscad.org wrote:

Objects do NOT have parents so I am not sure what you're talking
about? An object is a flat set of key-value pairs. There is no
hierarchy.

Then they shouldn't be called objects. At least 99% of people who
have or will use OpenSCAD will associate objects with the mainstream
object-oriented languages which all put inheritance front-and-center.
It's day-one "learning Python" material. I don't think there's a
single language with a "this" keyword that doesn't have inheritance.
You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to
keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of
OpenSCAD? There's often (but not always) a trade-off. Brainfuck is
very simple to implement. You can solve some very complicated
analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly
uglier and slower. My question is while settle for "this" when
generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of
code I'd like to write less than this kind of boiler plate code. I
wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I
wrote shows that, with a couple helper functions, the proposed
object() does not result in substantially simpler code and by the
maxim "keep OpenSCAD as simple as possible, but not simpler",
shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit
desperate and feel this is going way off the track and taking way
too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR
you make a fully working counter PR so people can play with the
proposals and compare. I find that you're now just dropping
disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the
time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because
people are unfamiliar with other systems. If SCAD was a hybrid
procedural/OO lisp with mutable values like JavaScript, then it'd be
fine to copy their semantics. But it's not and I think you're going
down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor
it using the proposed "this" approach or the one I'm proposing,
because ultimately what we want is what makes it easier and faster to
write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy
to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and
forth and then nothing happens again because everything got so
complicated. I think this partly happened with OEP8 and that spent a
lot of time in discussion. There are very good, some crucial, ideas
in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch
citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross

Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please
forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss
discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different
direction/syntax you took but then at the end I saw that you came
to the conclusion you could do all this also with 'this' and
object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the
object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression.
I am currently working on a PR for this. This will allow builders
to also call modules, have modules as variables, and hopefully
modules as methods when we can finally close [the $]this
discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss
discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args,
here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in
argument handling, but I'm not understanding what that means at
all.  Partly that's presentation; is this intended to be how the
library would say something, or how the caller would invoke the
function?
If the former, I don't understand what it means.  If the latter,
are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because
it's better for the user, but because it's better for the
maintainers and not worse for the users. (I would assume we'd add
using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module
in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the
suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to
use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change
from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
  profile = [
      [  -1/2, -0.77],
      [ -7/16, -0.75],
      [  5/16,  0],
      [  7/16,  0],
      [  7/16, -0.75],
      [  1/ 2, -0.77],
  ];
  generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument
name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
  let ( profile = [
      [  -1/2, -0.77],
      [ -7/16, -0.75],
      [  5/16,  0],
      [  7/16,  0],
      [  7/16, -0.75],
      [  1/ 2, -0.77],
  ])
  struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and
positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
  assert(is_list(profile)) // And other tests independent of
other values
  struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin,
orient) =
  assert(/* tests related to the parameters*/)
  let(
      l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
         is_undef(spin) ? [] : [["spin", spin]],
         is_undef(orient) ? [] : [["orient", orient]])
      )
  struct(o,l);

Okay, not in love with the repetition in there. But there are
some improvements here:

  1. Some validation can now stop cluttering the top of so many
    functions/modules.
  2. The get_radius function doesn't need its args filled out
    every time
  3. We're reusing attachable instead of needing to redundantly
    pass it so many args every single time and in every function and
    module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the
namespace of positioning? I didn't intend it to, but it would do
the wrong thing as written; only the calls in the new() methods
should add the namespace to the method lookup. This might be best
as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
  let ( profile = [
      [  -1/2, -0.77],
      [ -7/16, -0.75],
      [  5/16,  0],
      [  7/16,  0],
      [  7/16, -0.75],
      [  1/ 2, -0.77],
  ])
  object(new_generic_threaded_rod_builder().set_profile(profile),
/* all methods on buttress_threaded_nut_builder must be defined
here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
  assert(is_list(profile)) // And other tests independent of
other values
  object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
  assert(/* tests related to the parameters*/)
  let(
      l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
         is_undef(spin) ? [] : [["spin", spin]],
         is_undef(orient) ? [] : [["orient", orient]])
      )
  struct(this,l);

module reify_buttress_threaded_nut(obj) {
  reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
  reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules
are annoying. For this usage the inability to write your own
generic methods dispatching on other object types does not hinder
anything. The shared namespace means methods and values must have
unique names. I think this is supposed to solve name conflicts by
having you only need one unique name per file and you put all
your constants in there? And methods, I guess, so at the top of
f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
  top_level_constant = 27,
  function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
  bosl2_threading.top_level_constant; // for whatever reason
  reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org

After learning and using three different object-oriented languages, I found they all behave a little differently with respect to "OO principles". If OpenSCAD's objects are yet again different, no problem, I just go with the flow. The key thing here for me is, "do OpenSCAD objects let me do something that facilitates my modeling?"  If 'yes', then I'll figure out how it works, however it works.  Right now, I'm not seeing such, but that's okay, I'm just one use-case. Glenn On 8/22/2025 8:14 AM, Peter Kriens via Discuss wrote: > This discussion is imho a perfect example of bikeshedding > <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this > forever on a forum like this because we're all highly experienced & > opinionated but in the mean time OpenSCAD has no KV pairs ... The > enemy of good is 'better' ... This is exactly how the OEP8 effort got > derailed more than 2 years ago. At a certain moment everybody gets > confused and it dies. > > I suggest you make a working PR so we can play with concrete proposals > instead of trying to infer what your sketches mean. If I can use KV > pairs and get some concise way to make methods I happily close my PR > and support yours. > > I personally would appreciate a time box so we can make sure this does > not get delayed another two years again. > > Peter > > >> On 21 Aug 2025, at 23:18, Cory Cross via Discuss >> <discuss@lists.openscad.org> wrote: >> >> >> >> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss >> <discuss@lists.openscad.org> wrote: >>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss >>>> <discuss@lists.openscad.org> wrote: >>> Objects do NOT have parents so I am not sure what you're talking >>> about? An object is a flat set of key-value pairs. There is no >>> hierarchy. >> >> Then they shouldn't be called objects. At least 99% of people who >> have or will use OpenSCAD will associate objects with the mainstream >> object-oriented languages which all put inheritance front-and-center. >> It's day-one "learning Python" material. I don't think there's a >> single language with a "this" keyword that doesn't have inheritance. >> You're setting up people for confusion. >> >>> We are very intentionally not implementing a full blow OO system to >>> keep OpenSCAD as simple as possible, but not simpler. >> >> Following this logic, not adding it is simpler. >> >> What do you want to be simple: writing SCAD or the implementation of >> OpenSCAD? There's often (but not always) a trade-off. Brainfuck is >> very simple to implement. You can solve some very complicated >> analyses in a single line of Mathematica. >> >>>> So we already have the power to do builders, it's just slightly >>>> uglier and slower. My question is while settle for "this" when >>>> generic functions are even nicer? >>> >>> Slightly??? We must live in another universe. There are few types of >>> code I'd like to write less than this kind of boiler plate code. I >>> wrote several of these 'OO' systems but they were quite ugly. >> >> "this" refers to the proposal to add a "this" keyword. But what I >> wrote shows that, with a couple helper functions, the proposed >> object() does not result in substantially simpler code and by the >> maxim "keep OpenSCAD as simple as possible, but not simpler", >> shouldn't be added. >> >>> Then you threw in an a-bom ... and another one. I am getting a bit >>> desperate and feel this is going way off the track and taking way >>> too much of my time ... >>> >>> I am new here so I might not understand the mores in this project. >> >> I am new as well (though a user for many years). >> >>> However, in other projects I am used that if you want to derail a PR >>> you make a fully working counter PR so people can play with the >>> proposals and compare. I find that you're now just dropping >>> disruptive ideas ... >> >> I've not found any other discussion of OEP8 and wasn't active at the >> time anyway. I am discussing now because now is when I'm here. >> >> It's my impression the "this" keyword is just being added because >> people are unfamiliar with other systems. If SCAD was a hybrid >> procedural/OO lisp with mutable values like JavaScript, then it'd be >> fine to copy their semantics. But it's not and I think you're going >> down the wrong road. >> >> I'm trying to prove it by picking bosl2 and showing how I'd refactor >> it using the proposed "this" approach or the one I'm proposing, >> because ultimately what we want is what makes it easier and faster to >> write correct code, right? >> >> Everything I've proposed is quite easy to implement and I'll be happy >> to do it and/or collaborate on it. >> >>> The danger here is that we spend a lot of time talking back and >>> forth and then nothing happens again because everything got so >>> complicated. I think this partly happened with OEP8 and that spent a >>> lot of time in discussion. There are very good, some crucial, ideas >>> in that PR that has been idling since 2023. >> >> I also think it's important to keep momentum up. >> >>> I know I can be a bit blunt but you can blame it on my Dutch >>> citizenship ;-) >> >> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >> >> - Cory Cross >> >>> >>> Peter >>> >>> >>> >>>> >>>> Actually, plists are better because you can get super methods :-) >>>> >>>> >>>> I'm on my phone composing this without Internet access, so please >>>> forgive the formatting and mild syntax errors. >>>> >>>> >>>> >>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss >>>> <discuss@lists.openscad.org> wrote: >>>>> You flabbergasted me with the completely different >>>>> direction/syntax you took but then at the end I saw that you came >>>>> to the conclusion you could do all this also with 'this' and >>>>> `object`? Not good for my blood pressure! :-) >>>>> >>>>> I like the builder approach and it is one of my drivers for the >>>>> object work and $this. >>>>> >>>>> BTW, notice that OEP8 also proposed to have modules as expression. >>>>> I am currently working on a PR for this. This will allow builders >>>>> to also call modules, have modules as variables, and hopefully >>>>> modules as methods when we can finally close [the $]this >>>>> discussion ... >>>>> >>>>> Peter Kriens >>>>> >>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss >>>>>> <discuss@lists.openscad.org> wrote: >>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>> bosl2::threading::nut_builder::new(required, args, >>>>>>>> here)->optional_generic_arg(its_value)->reify(); >>>>>>> I'm very sympathetic to the desire to reduce repetition in >>>>>>> argument handling, but I'm not understanding what that means at >>>>>>> all.  Partly that's presentation; is this intended to be how the >>>>>>> library would say something, or how the caller would invoke the >>>>>>> function? >>>>>>> If the former, I don't understand what it means.  If the latter, >>>>>>> are you seriously suggesting this as a replacement for >>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>>> I am suggesting it as a replacement for the latter; not because >>>>>> it's better for the user, but because it's better for the >>>>>> maintainers and not worse for the users. (I would assume we'd add >>>>>> `using bosl2::threading` to shorten names, at some point). >>>>>> >>>>>> As a practical example, here is a invocation of a threaded module >>>>>> in my code: >>>>>> >>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>> >>>>>> here's how I would do it with the builder pattern and the >>>>>> suggested OO approach: >>>>>> >>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>> >>>>>> You don't have to use the builder pattern. You could choose to >>>>>> use objects as a replacement for Python's **kwargs: >>>>>> >>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>> >>>>>> In this case, buttress_threaded_nut's implementation would change >>>>>> from 50 lines to 11: >>>>>> >>>>>> module buttress_threaded_nut(kwargs) { >>>>>>   profile = [ >>>>>>       [  -1/2, -0.77], >>>>>>       [ -7/16, -0.75], >>>>>>       [  5/16,  0], >>>>>>       [  7/16,  0], >>>>>>       [  7/16, -0.75], >>>>>>       [  1/ 2, -0.77], >>>>>>   ]; >>>>>>   generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>> } >>>>>> >>>>>> Of course, this isn't OO and makes it harder to detect argument >>>>>> name typos and such. >>>>>> >>>>>> The builder pattern could be implemented as so: >>>>>> >>>>>> // namespace for buttress_threaded_nut_builder >>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>> function new() = >>>>>>   let ( profile = [ >>>>>>       [  -1/2, -0.77], >>>>>>       [ -7/16, -0.75], >>>>>>       [  5/16,  0], >>>>>>       [  7/16,  0], >>>>>>       [  7/16, -0.75], >>>>>>       [  1/ 2, -0.77], >>>>>>   ]) >>>>>>   struct(obj)->profile(profile); >>>>>> >>>>>> so not any more or less difficult to write. What do profile and >>>>>> positioning look like? >>>>>> >>>>>> // namespace of generic_threaded_rod_builder >>>>>> function profile(o is builder_obj, profile) = >>>>>>   assert(is_list(profile)) // And other tests independent of >>>>>> other values >>>>>>   struct(o,profile=profile); >>>>>> >>>>>> // namespace of attachable_builder >>>>>> function positioning(o is attachable_builder_obj, anchor, spin, >>>>>> orient) = >>>>>>   assert(/* tests related to the parameters*/) >>>>>>   let( >>>>>>       l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>          is_undef(spin) ? [] : [["spin", spin]], >>>>>>          is_undef(orient) ? [] : [["orient", orient]]) >>>>>>       ) >>>>>>   struct(o,l); >>>>>> >>>>>> Okay, not in love with the repetition in there. But there are >>>>>> some improvements here: >>>>>> >>>>>> 1. Some validation can now stop cluttering the top of so many >>>>>> functions/modules. >>>>>> 2. The `get_radius` function doesn't need its args filled out >>>>>> every time >>>>>> 3. We're reusing attachable instead of needing to redundantly >>>>>> pass it so many args every single time and in every function and >>>>>> module signature. >>>>>> 4. We can match on the old types and convert to objects as needed >>>>>> >>>>>> Unsolved issue: why would -> method invocation not look at the >>>>>> namespace of positioning? I didn't intend it to, but it would do >>>>>> the wrong thing as written; only the calls in the new() methods >>>>>> should add the namespace to the method lookup. This might be best >>>>>> as `object` vs `struct` keywords. >>>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>>> >>>>>> So how would I write this with `this` as currently proposed? >>>>>> >>>>>> function buttress_threaded_nut_builder = >>>>>>   let ( profile = [ >>>>>>       [  -1/2, -0.77], >>>>>>       [ -7/16, -0.75], >>>>>>       [  5/16,  0], >>>>>>       [  7/16,  0], >>>>>>       [  7/16, -0.75], >>>>>>       [  1/ 2, -0.77], >>>>>>   ]) >>>>>>   object(new_generic_threaded_rod_builder().set_profile(profile), >>>>>> /* all methods on buttress_threaded_nut_builder must be defined >>>>>> here */); >>>>>> >>>>>> // in method list of generic_threaded_rod_builder >>>>>> function set_profile(profile) = >>>>>>   assert(is_list(profile)) // And other tests independent of >>>>>> other values >>>>>>   object(this,profile=profile); >>>>>> >>>>>> // in method list of attachable_builder >>>>>> function set_positioning(anchor, spin, orient) = >>>>>>   assert(/* tests related to the parameters*/) >>>>>>   let( >>>>>>       l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>          is_undef(spin) ? [] : [["spin", spin]], >>>>>>          is_undef(orient) ? [] : [["orient", orient]]) >>>>>>       ) >>>>>>   struct(this,l); >>>>>> >>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>   reify_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> >>>>>> module reify_threaded_nut(obj) { >>>>>>   reify_generic_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>> >>>>>> >>>>>> ... >>>>>> >>>>>> Less different than I thought. Cascading explicit reify modules >>>>>> are annoying. For this usage the inability to write your own >>>>>> generic methods dispatching on other object types does not hinder >>>>>> anything. The shared namespace means methods and values must have >>>>>> unique names. I think this is supposed to solve name conflicts by >>>>>> having you only need one unique name per file and you put all >>>>>> your constants in there? And methods, I guess, so at the top of >>>>>> f.ex. bosl2/threading.scad there would be: >>>>>> >>>>>> bosl2_threading = object( >>>>>>   top_level_constant = 27, >>>>>>   function buttress_threaded_nut_builder() = >>>>>> ... >>>>>> ); >>>>>> >>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>   bosl2_threading.top_level_constant; // for whatever reason >>>>>>   reify_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>> _______________________________________________ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email todiscuss-leave@lists.openscad.org >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email todiscuss-leave@lists.openscad.org > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email todiscuss-leave@lists.openscad.org
LM
Leonard Martin Struttmann
Fri, Aug 22, 2025 6:05 PM

Let's stop trying to make them objects and just call them dictionary, map,
associative array, or any of the many other names for
this: https://en.m.wikipedia.org/wiki/Associative_array
https://en.m.wikipedia.org/wiki/Associative_array

I very much agree with this proposal! This will avoid confusion.

*Let's stop trying to make them objects and just call them dictionary, map, associative array, or any of the many other names for this: https://en.m.wikipedia.org/wiki/Associative_array <https://en.m.wikipedia.org/wiki/Associative_array>* I very much agree with this proposal! This will avoid confusion.
CC
Cory Cross
Fri, Aug 22, 2025 8:17 PM

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

This discussion is imho a perfect example of bikeshedding https://en.wiktionary.org/wiki/bikeshedding. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs

As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of struct_var.member it's l(struct_var,"member"). That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key.

  • Cory

... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies.

I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours.

I personally would appreciate a time box so we can make sure this does not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:
Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross

Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();
I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);
I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >This discussion is imho a perfect example of bikeshedding <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of `struct_var.member` it's `l(struct_var,"member")`. That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key. - Cory > ... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies. > >I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours. > >I personally would appreciate a time box so we can make sure this does not get delayed another two years again. > > Peter > > >> On 21 Aug 2025, at 23:18, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >> >> >> >> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. >> >> Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. >> >>> We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. >> >> Following this logic, not adding it is simpler. >> >> What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. >> >>>> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? >>> >>> Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. >> >> "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. >> >>> Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... >>> >>> I am new here so I might not understand the mores in this project. >> >> I am new as well (though a user for many years). >> >>> However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... >> >> I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. >> >> It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. >> >> I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? >> >> Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. >> >>> The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. >> >> I also think it's important to keep momentum up. >> >>> I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) >> >> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >> >> - Cory Cross >> >>> >>> Peter >>> >>> >>> >>>> >>>> Actually, plists are better because you can get super methods :-) >>>> >>>> >>>> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >>>> >>>> >>>> >>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>>>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>>>> >>>>> I like the builder approach and it is one of my drivers for the object work and $this. >>>>> >>>>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>>>> >>>>> Peter Kriens >>>>> >>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>>>> >>>>>> As a practical example, here is a invocation of a threaded module in my code: >>>>>> >>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>> >>>>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>>>> >>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>> >>>>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>>>> >>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>> >>>>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>>>> >>>>>> module buttress_threaded_nut(kwargs) { >>>>>> profile = [ >>>>>> [ -1/2, -0.77], >>>>>> [ -7/16, -0.75], >>>>>> [ 5/16, 0], >>>>>> [ 7/16, 0], >>>>>> [ 7/16, -0.75], >>>>>> [ 1/ 2, -0.77], >>>>>> ]; >>>>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>> } >>>>>> >>>>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>>>> >>>>>> The builder pattern could be implemented as so: >>>>>> >>>>>> // namespace for buttress_threaded_nut_builder >>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>> function new() = >>>>>> let ( profile = [ >>>>>> [ -1/2, -0.77], >>>>>> [ -7/16, -0.75], >>>>>> [ 5/16, 0], >>>>>> [ 7/16, 0], >>>>>> [ 7/16, -0.75], >>>>>> [ 1/ 2, -0.77], >>>>>> ]) >>>>>> struct(obj)->profile(profile); >>>>>> >>>>>> so not any more or less difficult to write. What do profile and positioning look like? >>>>>> >>>>>> // namespace of generic_threaded_rod_builder >>>>>> function profile(o is builder_obj, profile) = >>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>> struct(o,profile=profile); >>>>>> >>>>>> // namespace of attachable_builder >>>>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>>>> assert(/* tests related to the parameters*/) >>>>>> let( >>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>> ) >>>>>> struct(o,l); >>>>>> >>>>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>>>> >>>>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>>>> 2. The `get_radius` function doesn't need its args filled out every time >>>>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>>>> 4. We can match on the old types and convert to objects as needed >>>>>> >>>>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>>> >>>>>> So how would I write this with `this` as currently proposed? >>>>>> >>>>>> function buttress_threaded_nut_builder = >>>>>> let ( profile = [ >>>>>> [ -1/2, -0.77], >>>>>> [ -7/16, -0.75], >>>>>> [ 5/16, 0], >>>>>> [ 7/16, 0], >>>>>> [ 7/16, -0.75], >>>>>> [ 1/ 2, -0.77], >>>>>> ]) >>>>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>>>> >>>>>> // in method list of generic_threaded_rod_builder >>>>>> function set_profile(profile) = >>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>> object(this,profile=profile); >>>>>> >>>>>> // in method list of attachable_builder >>>>>> function set_positioning(anchor, spin, orient) = >>>>>> assert(/* tests related to the parameters*/) >>>>>> let( >>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>> ) >>>>>> struct(this,l); >>>>>> >>>>>> module reify_buttress_threaded_nut(obj) { >>>>>> reify_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> >>>>>> module reify_threaded_nut(obj) { >>>>>> reify_generic_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>> >>>>>> >>>>>> ... >>>>>> >>>>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>>>> >>>>>> bosl2_threading = object( >>>>>> top_level_constant = 27, >>>>>> function buttress_threaded_nut_builder() = >>>>>> ... >>>>>> ); >>>>>> >>>>>> module reify_buttress_threaded_nut(obj) { >>>>>> bosl2_threading.top_level_constant; // for whatever reason >>>>>> reify_threaded_nut(obj); >>>>>> } >>>>>> >>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>> _______________________________________________ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org>
V
vulcan_ (at) mac.com
Fri, Aug 22, 2025 8:17 PM

sometimes i want my program (or scad script) to create and /or manipulate related data items with methods that are the only access to those items.

the requirement that methods be the interface into encapsulated data means that i need something more than an associative array (from my reading of assoc arrays at least, have i understood them correctly?)

But to make that work an object’s methods must be able to access its own elements, thus the need for $this, %%this%% or any other syntax that makes sense.

i don’t agree that using the word “object” implies that we must also have inheritance .. I am happy to use the word “object” for what has already been implemented .. even if $this is not added

sometimes i want my program (or scad script) to create and /or manipulate related data items with methods that are the **only** access to those items. the requirement that *methods* be the interface into encapsulated data means that i need something more than an associative array (from *my* reading of assoc arrays at least, have i understood them correctly?) But to make that work an object’s methods must be able to access its own elements, thus the need for $this, %%this%% or any other syntax that makes sense. i don’t agree that using the word “object” implies that we *must also* have inheritance .. I am happy to use the word “object” for what has already been implemented .. even if $this is not added
JB
Jon Bondy
Fri, Aug 22, 2025 8:31 PM

From

https://en.wikipedia.org/wiki/Object-oriented_programming

we find

*In the 1970s, the first version of the Smalltalk
https://en.wikipedia.org/wiki/Smalltalk programming language was
developed at Xerox PARC https://en.wikipedia.org/wiki/Xerox_PARC by
Alan Kay https://en.wikipedia.org/wiki/Alan_Kay, Dan Ingalls
https://en.wikipedia.org/wiki/Dan_Ingalls and Adele Goldberg
https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist).
Smalltalk-72 was notable for use of objects at the language level and
its graphical development environment.^[20]
https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-Bertrand_Meyer_2009_329-20
Smalltalk was a fully dynamic system, allowing users to create and
modify classes as they worked.^[21]
https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-21
Much of the theory of OOP was developed in the context of Smalltalk, for
example multiple inheritance.^[22]
https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-22 *

Having worked with these concepts for decades, Vulcan and I disagree on
this point.  I guess we all come from different backgrounds, and words
have different meanings for us.  But if we EVER want REAL objects in
OpenSCAD, then we need to call this initial attempt something different.

Jon

On 8/22/2025 4:17 PM, vulcan_--- via Discuss wrote:

i don’t agree that using the word “object” implies that we /must also/
have inheritance

--
This email has been checked for viruses by AVG antivirus software.
www.avg.com

From https://en.wikipedia.org/wiki/Object-oriented_programming we find *In the 1970s, the first version of the Smalltalk <https://en.wikipedia.org/wiki/Smalltalk> programming language was developed at Xerox PARC <https://en.wikipedia.org/wiki/Xerox_PARC> by Alan Kay <https://en.wikipedia.org/wiki/Alan_Kay>, Dan Ingalls <https://en.wikipedia.org/wiki/Dan_Ingalls> and Adele Goldberg <https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist)>. Smalltalk-72 was notable for use of objects at the language level and its graphical development environment.^[20] <https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-Bertrand_Meyer_2009_329-20> Smalltalk was a fully dynamic system, allowing users to create and modify classes as they worked.^[21] <https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-21> Much of the theory of OOP was developed in the context of Smalltalk, for example multiple inheritance.^[22] <https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-22> * Having worked with these concepts for decades, Vulcan and I disagree on this point.  I guess we all come from different backgrounds, and words have different meanings for us.  But if we EVER want REAL objects in OpenSCAD, then we need to call this initial attempt something different. Jon On 8/22/2025 4:17 PM, vulcan_--- via Discuss wrote: > > i don’t agree that using the word “object” implies that we /must also/ > have inheritance > > -- This email has been checked for viruses by AVG antivirus software. www.avg.com
HW
Harvey white
Fri, Aug 22, 2025 9:53 PM

The problem is not what "we" think it means, it's more what all the rest
of "them" are likely to think.

picking a name with few expectations may produce less assumptions.

Harvey

On 8/22/2025 4:31 PM, Jon Bondy via Discuss wrote:

From

https://en.wikipedia.org/wiki/Object-oriented_programming

we find

*In the 1970s, the first version of the Smalltalk
https://en.wikipedia.org/wiki/Smalltalk programming language was
developed at Xerox PARC https://en.wikipedia.org/wiki/Xerox_PARC by
Alan Kay https://en.wikipedia.org/wiki/Alan_Kay, Dan Ingalls
https://en.wikipedia.org/wiki/Dan_Ingalls and Adele Goldberg
https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist).
Smalltalk-72 was notable for use of objects at the language level and
its graphical development environment.^[20]
https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-Bertrand_Meyer_2009_329-20
Smalltalk was a fully dynamic system, allowing users to create and
modify classes as they worked.^[21]
https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-21
Much of the theory of OOP was developed in the context of Smalltalk,
for example multiple inheritance.^[22]
https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-22 *

Having worked with these concepts for decades, Vulcan and I disagree
on this point.  I guess we all come from different backgrounds, and
words have different meanings for us.  But if we EVER want REAL
objects in OpenSCAD, then we need to call this initial attempt
something different.

Jon

On 8/22/2025 4:17 PM, vulcan_--- via Discuss wrote:

i don’t agree that using the word “object” implies that we /must
also/ have inheritance

The problem is not what "we" think it means, it's more what all the rest of "them" are likely to think. picking a name with few expectations may produce less assumptions. Harvey On 8/22/2025 4:31 PM, Jon Bondy via Discuss wrote: > > From > > https://en.wikipedia.org/wiki/Object-oriented_programming > > we find > > *In the 1970s, the first version of the Smalltalk > <https://en.wikipedia.org/wiki/Smalltalk> programming language was > developed at Xerox PARC <https://en.wikipedia.org/wiki/Xerox_PARC> by > Alan Kay <https://en.wikipedia.org/wiki/Alan_Kay>, Dan Ingalls > <https://en.wikipedia.org/wiki/Dan_Ingalls> and Adele Goldberg > <https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist)>. > Smalltalk-72 was notable for use of objects at the language level and > its graphical development environment.^[20] > <https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-Bertrand_Meyer_2009_329-20> > Smalltalk was a fully dynamic system, allowing users to create and > modify classes as they worked.^[21] > <https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-21> > Much of the theory of OOP was developed in the context of Smalltalk, > for example multiple inheritance.^[22] > <https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-22> * > > Having worked with these concepts for decades, Vulcan and I disagree > on this point.  I guess we all come from different backgrounds, and > words have different meanings for us.  But if we EVER want REAL > objects in OpenSCAD, then we need to call this initial attempt > something different. > > Jon > > > On 8/22/2025 4:17 PM, vulcan_--- via Discuss wrote: >> >> i don’t agree that using the word “object” implies that we /must >> also/ have inheritance >> >> > > <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> > Virus-free.www.avg.com > <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> > > > <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2> > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
AM
Adrian Mariano
Fri, Aug 22, 2025 10:41 PM

Yeah, you can implement structs as things stand.  I've done it.  And the
syntax struct_val(struct_name, "key_name") is horrible and the clunky
nature of the whole thing for practical use is such that I use them only as
a last resort.  I find a huge readability difference between code that
says thing.foo+thing.bar^2f(thing.baz-thing.other) as compared to
struct_val(thing,"foo")+struct_val(thing,"bar")^2
f(struct_val(thing,"baz")-struct_val(thing,"other")).
I'm sure also that performance is bad enough the userspace implementation
that it shouldn't be used any time performance matters.  I personally
don't care what the new feature is called.  Rename it map, dict or struct
if object offends people.

On Fri, Aug 22, 2025 at 4:18 PM Cory Cross via Discuss <
discuss@lists.openscad.org> wrote:

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss <
discuss@lists.openscad.org> wrote:

This discussion is imho a perfect example of bikeshedding <

https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever
on a forum like this because we're all highly experienced & opinionated but
in the mean time OpenSCAD has no KV pairs

As I pointed out before, this isn't true. If you want kv pairs it's
trivially implemented with alists. Instead of struct_var.member it's
l(struct_var,"member"). That's 17 vs 22 characters or 29% more for a
typical case. As a bonus, this userspace method is backwards-compatible and
forward-compatible with an optimized implementation which does also allow
struct_var.member syntax and allows any type as a key.

  • Cory

... The enemy of good is 'better' ... This is exactly how the OEP8

effort got derailed more than 2 years ago. At a certain moment everybody
gets confused and it dies.

I suggest you make a working PR so we can play with concrete proposals

instead of trying to infer what your sketches mean. If I can use KV pairs
and get some concise way to make methods I happily close my PR and support
yours.

I personally would appreciate a time box so we can make sure this does

not get delayed another two years again.

   Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss <

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <

Objects do NOT have parents so I am not sure what you're talking

about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have

or will use OpenSCAD will associate objects with the mainstream
object-oriented languages which all put inheritance front-and-center. It's
day-one "learning Python" material. I don't think there's a single language
with a "this" keyword that doesn't have inheritance. You're setting up
people for confusion.

We are very intentionally not implementing a full blow OO system to

keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of

OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very
simple to implement. You can solve some very complicated analyses in a
single line of Mathematica.

So we already have the power to do builders, it's just slightly

uglier and slower. My question is while settle for "this" when generic
functions are even nicer?

Slightly??? We must live in another universe. There are few types of

code I'd like to write less than this kind of boiler plate code. I wrote
several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote

shows that, with a couple helper functions, the proposed object() does not
result in substantially simpler code and by the maxim "keep OpenSCAD as
simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit

desperate and feel this is going way off the track and taking way too much
of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR

you make a fully working counter PR so people can play with the proposals
and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the

time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because

people are unfamiliar with other systems. If SCAD was a hybrid
procedural/OO lisp with mutable values like JavaScript, then it'd be fine
to copy their semantics. But it's not and I think you're going down the
wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it

using the proposed "this" approach or the one I'm proposing, because
ultimately what we want is what makes it easier and faster to write correct
code, right?

Everything I've proposed is quite easy to implement and I'll be happy

to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth

and then nothing happens again because everything got so complicated. I
think this partly happened with OEP8 and that spent a lot of time in
discussion. There are very good, some crucial, ideas in that PR that has
been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch

citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross
 Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please

forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <

You flabbergasted me with the completely different direction/syntax

you took but then at the end I saw that you came to the conclusion you
could do all this also with 'this' and object? Not good for my blood
pressure! :-)

I like the builder approach and it is one of my drivers for the

object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I

am currently working on a PR for this. This will allow builders to also
call modules, have modules as variables, and hopefully modules as methods
when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss <

On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args,

here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in

argument handling, but I'm not understanding what that means at all.
Partly that's presentation; is this intended to be how the library would
say something, or how the caller would invoke the function?

If the former, I don't understand what it means.  If the latter,

are you seriously suggesting this as a replacement for

threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because

it's better for the user, but because it's better for the maintainers and
not worse for the users. (I would assume we'd add using bosl2::threading
to shorten names, at some point).

As a practical example, here is a invocation of a threaded module

in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested

OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use

objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change

from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument

name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and

positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other

values

struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin,

orient) =

assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some

improvements here:

  1. Some validation can now stop cluttering the top of so many

functions/modules.

  1. The get_radius function doesn't need its args filled out every

time

  1. We're reusing attachable instead of needing to redundantly pass

it so many args every single time and in every function and module
signature.

  1. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the

namespace of positioning? I didn't intend it to, but it would do the wrong
thing as written; only the calls in the new() methods should add the
namespace to the method lookup. This might be best as object vs struct
keywords.

Maybe attachable should be a mixin instead of in the class

hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile),

/* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other

values

object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are

annoying. For this usage the inability to write your own generic methods
dispatching on other object types does not hinder anything. The shared
namespace means methods and values must have unique names. I think this is
supposed to solve name conflicts by having you only need one unique name
per file and you put all your constants in there? And methods, I guess, so
at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Yeah, you can implement structs as things stand. I've done it. And the syntax struct_val(struct_name, "key_name") is horrible and the clunky nature of the whole thing for practical use is such that I use them only as a last resort. I find a huge readability difference between code that says thing.foo+thing.bar^2*f(thing.baz-thing.other) as compared to struct_val(thing,"foo")+struct_val(thing,"bar")^2*f(struct_val(thing,"baz")-struct_val(thing,"other")). I'm sure also that performance is bad enough the userspace implementation that it shouldn't be used any time performance matters. I personally don't care what the new feature is called. Rename it map, dict or struct if object offends people. On Fri, Aug 22, 2025 at 4:18 PM Cory Cross via Discuss < discuss@lists.openscad.org> wrote: > > > On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss < > discuss@lists.openscad.org> wrote: > >This discussion is imho a perfect example of bikeshedding < > https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever > on a forum like this because we're all highly experienced & opinionated but > in the mean time OpenSCAD has no KV pairs > > As I pointed out before, this isn't true. If you want kv pairs it's > trivially implemented with alists. Instead of `struct_var.member` it's > `l(struct_var,"member")`. That's 17 vs 22 characters or 29% more for a > typical case. As a bonus, this userspace method is backwards-compatible and > forward-compatible with an optimized implementation which does also allow > struct_var.member syntax and allows any type as a key. > > - Cory > > > > > ... The enemy of good is 'better' ... This is exactly how the OEP8 > effort got derailed more than 2 years ago. At a certain moment everybody > gets confused and it dies. > > > >I suggest you make a working PR so we can play with concrete proposals > instead of trying to infer what your sketches mean. If I can use KV pairs > and get some concise way to make methods I happily close my PR and support > yours. > > > >I personally would appreciate a time box so we can make sure this does > not get delayed another two years again. > > > > Peter > > > > > >> On 21 Aug 2025, at 23:18, Cory Cross via Discuss < > discuss@lists.openscad.org> wrote: > >> > >> > >> > >> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss < > discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: > >>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss < > discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: > >>> Objects do NOT have parents so I am not sure what you're talking > about? An object is a flat set of key-value pairs. There is no hierarchy. > >> > >> Then they shouldn't be called objects. At least 99% of people who have > or will use OpenSCAD will associate objects with the mainstream > object-oriented languages which all put inheritance front-and-center. It's > day-one "learning Python" material. I don't think there's a single language > with a "this" keyword that doesn't have inheritance. You're setting up > people for confusion. > >> > >>> We are very intentionally not implementing a full blow OO system to > keep OpenSCAD as simple as possible, but not simpler. > >> > >> Following this logic, not adding it is simpler. > >> > >> What do you want to be simple: writing SCAD or the implementation of > OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very > simple to implement. You can solve some very complicated analyses in a > single line of Mathematica. > >> > >>>> So we already have the power to do builders, it's just slightly > uglier and slower. My question is while settle for "this" when generic > functions are even nicer? > >>> > >>> Slightly??? We must live in another universe. There are few types of > code I'd like to write less than this kind of boiler plate code. I wrote > several of these 'OO' systems but they were quite ugly. > >> > >> "this" refers to the proposal to add a "this" keyword. But what I wrote > shows that, with a couple helper functions, the proposed object() does not > result in substantially simpler code and by the maxim "keep OpenSCAD as > simple as possible, but not simpler", shouldn't be added. > >> > >>> Then you threw in an a-bom ... and another one. I am getting a bit > desperate and feel this is going way off the track and taking way too much > of my time ... > >>> > >>> I am new here so I might not understand the mores in this project. > >> > >> I am new as well (though a user for many years). > >> > >>> However, in other projects I am used that if you want to derail a PR > you make a fully working counter PR so people can play with the proposals > and compare. I find that you're now just dropping disruptive ideas ... > >> > >> I've not found any other discussion of OEP8 and wasn't active at the > time anyway. I am discussing now because now is when I'm here. > >> > >> It's my impression the "this" keyword is just being added because > people are unfamiliar with other systems. If SCAD was a hybrid > procedural/OO lisp with mutable values like JavaScript, then it'd be fine > to copy their semantics. But it's not and I think you're going down the > wrong road. > >> > >> I'm trying to prove it by picking bosl2 and showing how I'd refactor it > using the proposed "this" approach or the one I'm proposing, because > ultimately what we want is what makes it easier and faster to write correct > code, right? > >> > >> Everything I've proposed is quite easy to implement and I'll be happy > to do it and/or collaborate on it. > >> > >>> The danger here is that we spend a lot of time talking back and forth > and then nothing happens again because everything got so complicated. I > think this partly happened with OEP8 and that spent a lot of time in > discussion. There are very good, some crucial, ideas in that PR that has > been idling since 2023. > >> > >> I also think it's important to keep momentum up. > >> > >>> I know I can be a bit blunt but you can blame it on my Dutch > citizenship ;-) > >> > >> I can be a bit blunt but you can blame it on my Dutch ancestry :-). > >> > >> - Cory Cross > >> > >>> > >>> Peter > >>> > >>> > >>> > >>>> > >>>> Actually, plists are better because you can get super methods :-) > >>>> > >>>> > >>>> I'm on my phone composing this without Internet access, so please > forgive the formatting and mild syntax errors. > >>>> > >>>> > >>>> > >>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss < > discuss@lists.openscad.org> wrote: > >>>>> You flabbergasted me with the completely different direction/syntax > you took but then at the end I saw that you came to the conclusion you > could do all this also with 'this' and `object`? Not good for my blood > pressure! :-) > >>>>> > >>>>> I like the builder approach and it is one of my drivers for the > object work and $this. > >>>>> > >>>>> BTW, notice that OEP8 also proposed to have modules as expression. I > am currently working on a PR for this. This will allow builders to also > call modules, have modules as variables, and hopefully modules as methods > when we can finally close [the $]this discussion ... > >>>>> > >>>>> Peter Kriens > >>>>> > >>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss < > discuss@lists.openscad.org> wrote: > >>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: > >>>>>>>> bosl2::threading::nut_builder::new(required, args, > here)->optional_generic_arg(its_value)->reify(); > >>>>>>> I'm very sympathetic to the desire to reduce repetition in > argument handling, but I'm not understanding what that means at all. > Partly that's presentation; is this intended to be how the library would > say something, or how the caller would invoke the function? > >>>>>>> If the former, I don't understand what it means. If the latter, > are you seriously suggesting this as a replacement for > >>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); > >>>>>> I am suggesting it as a replacement for the latter; not because > it's better for the user, but because it's better for the maintainers and > not worse for the users. (I would assume we'd add `using bosl2::threading` > to shorten names, at some point). > >>>>>> > >>>>>> As a practical example, here is a invocation of a threaded module > in my code: > >>>>>> > >>>>>> > buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); > >>>>>> > >>>>>> here's how I would do it with the builder pattern and the suggested > OO approach: > >>>>>> > >>>>>> > buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; > >>>>>> > >>>>>> You don't have to use the builder pattern. You could choose to use > objects as a replacement for Python's **kwargs: > >>>>>> > >>>>>> > buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); > >>>>>> > >>>>>> In this case, buttress_threaded_nut's implementation would change > from 50 lines to 11: > >>>>>> > >>>>>> module buttress_threaded_nut(kwargs) { > >>>>>> profile = [ > >>>>>> [ -1/2, -0.77], > >>>>>> [ -7/16, -0.75], > >>>>>> [ 5/16, 0], > >>>>>> [ 7/16, 0], > >>>>>> [ 7/16, -0.75], > >>>>>> [ 1/ 2, -0.77], > >>>>>> ]; > >>>>>> generic_threaded_nut(struct(kwargs, profile=profile)); > >>>>>> } > >>>>>> > >>>>>> Of course, this isn't OO and makes it harder to detect argument > name typos and such. > >>>>>> > >>>>>> The builder pattern could be implemented as so: > >>>>>> > >>>>>> // namespace for buttress_threaded_nut_builder > >>>>>> obj = struct(generic_threaded_nut_builder::new()); > >>>>>> function new() = > >>>>>> let ( profile = [ > >>>>>> [ -1/2, -0.77], > >>>>>> [ -7/16, -0.75], > >>>>>> [ 5/16, 0], > >>>>>> [ 7/16, 0], > >>>>>> [ 7/16, -0.75], > >>>>>> [ 1/ 2, -0.77], > >>>>>> ]) > >>>>>> struct(obj)->profile(profile); > >>>>>> > >>>>>> so not any more or less difficult to write. What do profile and > positioning look like? > >>>>>> > >>>>>> // namespace of generic_threaded_rod_builder > >>>>>> function profile(o is builder_obj, profile) = > >>>>>> assert(is_list(profile)) // And other tests independent of other > values > >>>>>> struct(o,profile=profile); > >>>>>> > >>>>>> // namespace of attachable_builder > >>>>>> function positioning(o is attachable_builder_obj, anchor, spin, > orient) = > >>>>>> assert(/* tests related to the parameters*/) > >>>>>> let( > >>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], > >>>>>> is_undef(spin) ? [] : [["spin", spin]], > >>>>>> is_undef(orient) ? [] : [["orient", orient]]) > >>>>>> ) > >>>>>> struct(o,l); > >>>>>> > >>>>>> Okay, not in love with the repetition in there. But there are some > improvements here: > >>>>>> > >>>>>> 1. Some validation can now stop cluttering the top of so many > functions/modules. > >>>>>> 2. The `get_radius` function doesn't need its args filled out every > time > >>>>>> 3. We're reusing attachable instead of needing to redundantly pass > it so many args every single time and in every function and module > signature. > >>>>>> 4. We can match on the old types and convert to objects as needed > >>>>>> > >>>>>> Unsolved issue: why would -> method invocation not look at the > namespace of positioning? I didn't intend it to, but it would do the wrong > thing as written; only the calls in the new() methods should add the > namespace to the method lookup. This might be best as `object` vs `struct` > keywords. > >>>>>> Maybe attachable should be a mixin instead of in the class > hierarchy. > >>>>>> > >>>>>> So how would I write this with `this` as currently proposed? > >>>>>> > >>>>>> function buttress_threaded_nut_builder = > >>>>>> let ( profile = [ > >>>>>> [ -1/2, -0.77], > >>>>>> [ -7/16, -0.75], > >>>>>> [ 5/16, 0], > >>>>>> [ 7/16, 0], > >>>>>> [ 7/16, -0.75], > >>>>>> [ 1/ 2, -0.77], > >>>>>> ]) > >>>>>> object(new_generic_threaded_rod_builder().set_profile(profile), > /* all methods on buttress_threaded_nut_builder must be defined here */); > >>>>>> > >>>>>> // in method list of generic_threaded_rod_builder > >>>>>> function set_profile(profile) = > >>>>>> assert(is_list(profile)) // And other tests independent of other > values > >>>>>> object(this,profile=profile); > >>>>>> > >>>>>> // in method list of attachable_builder > >>>>>> function set_positioning(anchor, spin, orient) = > >>>>>> assert(/* tests related to the parameters*/) > >>>>>> let( > >>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], > >>>>>> is_undef(spin) ? [] : [["spin", spin]], > >>>>>> is_undef(orient) ? [] : [["orient", orient]]) > >>>>>> ) > >>>>>> struct(this,l); > >>>>>> > >>>>>> module reify_buttress_threaded_nut(obj) { > >>>>>> reify_threaded_nut(obj); > >>>>>> } > >>>>>> > >>>>>> > >>>>>> module reify_threaded_nut(obj) { > >>>>>> reify_generic_threaded_nut(obj); > >>>>>> } > >>>>>> > >>>>>> > reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); > >>>>>> > >>>>>> > >>>>>> ... > >>>>>> > >>>>>> Less different than I thought. Cascading explicit reify modules are > annoying. For this usage the inability to write your own generic methods > dispatching on other object types does not hinder anything. The shared > namespace means methods and values must have unique names. I think this is > supposed to solve name conflicts by having you only need one unique name > per file and you put all your constants in there? And methods, I guess, so > at the top of f.ex. bosl2/threading.scad there would be: > >>>>>> > >>>>>> bosl2_threading = object( > >>>>>> top_level_constant = 27, > >>>>>> function buttress_threaded_nut_builder() = > >>>>>> ... > >>>>>> ); > >>>>>> > >>>>>> module reify_buttress_threaded_nut(obj) { > >>>>>> bosl2_threading.top_level_constant; // for whatever reason > >>>>>> reify_threaded_nut(obj); > >>>>>> } > >>>>>> > >>>>>> I've seen worse. I certainly can't say this rules out `this`. > >>>>>> _______________________________________________ > >>>>>> OpenSCAD mailing list > >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org > >>>>> > >>>>> _______________________________________________ > >>>>> OpenSCAD mailing list > >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org > >>>> _______________________________________________ > >>>> OpenSCAD mailing list > >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org > >>> _______________________________________________ > >>> OpenSCAD mailing list > >>> To unsubscribe send an email to discuss-leave@lists.openscad.org > <mailto:discuss-leave@lists.openscad.org> > >> _______________________________________________ > >> OpenSCAD mailing list > >> To unsubscribe send an email to discuss-leave@lists.openscad.org > <mailto:discuss-leave@lists.openscad.org> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
CC
Cory Cross
Fri, Aug 22, 2025 11:58 PM

And, of course it's already done in BOSL2: https://github.com/BelfrySCAD/BOSL2/wiki/structs.scad

Throw in l=struct_val; to shorten the number of characters to type.

On August 22, 2025 4:17:31 PM EDT, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

This discussion is imho a perfect example of bikeshedding https://en.wiktionary.org/wiki/bikeshedding. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs

As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of struct_var.member it's l(struct_var,"member"). That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key.

  • Cory

... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies.

I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours.

I personally would appreciate a time box so we can make sure this does not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross
Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

And, of course it's already done in BOSL2: https://github.com/BelfrySCAD/BOSL2/wiki/structs.scad Throw in `l=struct_val;` to shorten the number of characters to type. On August 22, 2025 4:17:31 PM EDT, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: > > >On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>This discussion is imho a perfect example of bikeshedding <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs > >As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of `struct_var.member` it's `l(struct_var,"member")`. That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key. > >- Cory > > > >> ... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies. >> >>I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours. >> >>I personally would appreciate a time box so we can make sure this does not get delayed another two years again. >> >> Peter >> >> >>> On 21 Aug 2025, at 23:18, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>> >>> >>> >>> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. >>> >>> Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. >>> >>>> We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. >>> >>> Following this logic, not adding it is simpler. >>> >>> What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. >>> >>>>> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? >>>> >>>> Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. >>> >>> "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. >>> >>>> Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... >>>> >>>> I am new here so I might not understand the mores in this project. >>> >>> I am new as well (though a user for many years). >>> >>>> However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... >>> >>> I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. >>> >>> It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. >>> >>> I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? >>> >>> Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. >>> >>>> The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. >>> >>> I also think it's important to keep momentum up. >>> >>>> I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) >>> >>> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >>> >>> - Cory Cross >>> >>>> >>>> Peter >>>> >>>> >>>> >>>>> >>>>> Actually, plists are better because you can get super methods :-) >>>>> >>>>> >>>>> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >>>>> >>>>> >>>>> >>>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>>>>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>>>>> >>>>>> I like the builder approach and it is one of my drivers for the object work and $this. >>>>>> >>>>>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>>>>> >>>>>> Peter Kriens >>>>>> >>>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>>>>> >>>>>>> As a practical example, here is a invocation of a threaded module in my code: >>>>>>> >>>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>>> >>>>>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>>>>> >>>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>>> >>>>>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>>>>> >>>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>>> >>>>>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>>>>> >>>>>>> module buttress_threaded_nut(kwargs) { >>>>>>> profile = [ >>>>>>> [ -1/2, -0.77], >>>>>>> [ -7/16, -0.75], >>>>>>> [ 5/16, 0], >>>>>>> [ 7/16, 0], >>>>>>> [ 7/16, -0.75], >>>>>>> [ 1/ 2, -0.77], >>>>>>> ]; >>>>>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>>> } >>>>>>> >>>>>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>>>>> >>>>>>> The builder pattern could be implemented as so: >>>>>>> >>>>>>> // namespace for buttress_threaded_nut_builder >>>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>>> function new() = >>>>>>> let ( profile = [ >>>>>>> [ -1/2, -0.77], >>>>>>> [ -7/16, -0.75], >>>>>>> [ 5/16, 0], >>>>>>> [ 7/16, 0], >>>>>>> [ 7/16, -0.75], >>>>>>> [ 1/ 2, -0.77], >>>>>>> ]) >>>>>>> struct(obj)->profile(profile); >>>>>>> >>>>>>> so not any more or less difficult to write. What do profile and positioning look like? >>>>>>> >>>>>>> // namespace of generic_threaded_rod_builder >>>>>>> function profile(o is builder_obj, profile) = >>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>> struct(o,profile=profile); >>>>>>> >>>>>>> // namespace of attachable_builder >>>>>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>>>>> assert(/* tests related to the parameters*/) >>>>>>> let( >>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>> ) >>>>>>> struct(o,l); >>>>>>> >>>>>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>>>>> >>>>>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>>>>> 2. The `get_radius` function doesn't need its args filled out every time >>>>>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>>>>> 4. We can match on the old types and convert to objects as needed >>>>>>> >>>>>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>>>> >>>>>>> So how would I write this with `this` as currently proposed? >>>>>>> >>>>>>> function buttress_threaded_nut_builder = >>>>>>> let ( profile = [ >>>>>>> [ -1/2, -0.77], >>>>>>> [ -7/16, -0.75], >>>>>>> [ 5/16, 0], >>>>>>> [ 7/16, 0], >>>>>>> [ 7/16, -0.75], >>>>>>> [ 1/ 2, -0.77], >>>>>>> ]) >>>>>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>>>>> >>>>>>> // in method list of generic_threaded_rod_builder >>>>>>> function set_profile(profile) = >>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>> object(this,profile=profile); >>>>>>> >>>>>>> // in method list of attachable_builder >>>>>>> function set_positioning(anchor, spin, orient) = >>>>>>> assert(/* tests related to the parameters*/) >>>>>>> let( >>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>> ) >>>>>>> struct(this,l); >>>>>>> >>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>> reify_threaded_nut(obj); >>>>>>> } >>>>>>> >>>>>>> >>>>>>> module reify_threaded_nut(obj) { >>>>>>> reify_generic_threaded_nut(obj); >>>>>>> } >>>>>>> >>>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>>> >>>>>>> >>>>>>> ... >>>>>>> >>>>>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>>>>> >>>>>>> bosl2_threading = object( >>>>>>> top_level_constant = 27, >>>>>>> function buttress_threaded_nut_builder() = >>>>>>> ... >>>>>>> ); >>>>>>> >>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>> bosl2_threading.top_level_constant; // for whatever reason >>>>>>> reify_threaded_nut(obj); >>>>>>> } >>>>>>> >>>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>>> _______________________________________________ >>>>>>> OpenSCAD mailing list >>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>> >>>>>> _______________________________________________ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> >_______________________________________________ >OpenSCAD mailing list >To unsubscribe send an email to discuss-leave@lists.openscad.org
AM
Adrian Mariano
Sat, Aug 23, 2025 12:06 AM

Perhaps you're not aware that I'm one of the BOSL2 authors, so when I say
"I implemented" that means it's in BOSL2.  Changing struct_val to l just
makes it more confusing to read because the function calls are now very
short. It's not about character count exactly---I type very fast---but
about syntactic simplicity and clarity---code readability.  Basically any
time I use these I end up writing foo = struct_val(thing,"foo") to avoid
the syntactic mess that arises from direct access to the structure.  (I am
not a fan of python dicts where you need quotes either.  I return
namedtuples from my functions instead of dicts.)

On Fri, Aug 22, 2025 at 7:58 PM Cory Cross via Discuss <
discuss@lists.openscad.org> wrote:

And, of course it's already done in BOSL2:
https://github.com/BelfrySCAD/BOSL2/wiki/structs.scad

Throw in l=struct_val; to shorten the number of characters to type.

On August 22, 2025 4:17:31 PM EDT, Cory Cross via Discuss <
discuss@lists.openscad.org> wrote:

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

This discussion is imho a perfect example of bikeshedding https://en.wiktionary.org/wiki/bikeshedding. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs

As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of struct_var.member it's l(struct_var,"member"). That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key.

  • Cory

... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies.

I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours.

I personally would appreciate a time box so we can make sure this does not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org discuss@lists.openscad.org>> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org discuss@lists.openscad.org>> wrote:

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross
Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.

OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org discuss-leave@lists.openscad.org>


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org discuss-leave@lists.openscad.org>


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Perhaps you're not aware that I'm one of the BOSL2 authors, so when I say "I implemented" that means it's in BOSL2. Changing struct_val to l just makes it more confusing to read because the function calls are now very short. It's not about character count exactly---I type very fast---but about syntactic simplicity and clarity---code readability. Basically any time I use these I end up writing foo = struct_val(thing,"foo") to avoid the syntactic mess that arises from direct access to the structure. (I am not a fan of python dicts where you need quotes either. I return namedtuples from my functions instead of dicts.) On Fri, Aug 22, 2025 at 7:58 PM Cory Cross via Discuss < discuss@lists.openscad.org> wrote: > And, of course it's already done in BOSL2: > https://github.com/BelfrySCAD/BOSL2/wiki/structs.scad > > Throw in `l=struct_val;` to shorten the number of characters to type. > > > On August 22, 2025 4:17:31 PM EDT, Cory Cross via Discuss < > discuss@lists.openscad.org> wrote: > >> >> >> On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >> >>> This discussion is imho a perfect example of bikeshedding <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs >>> >> >> As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of `struct_var.member` it's `l(struct_var,"member")`. That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key. >> >> - Cory >> >> >> >> ... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies. >>> >>> I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours. >>> >>> I personally would appreciate a time box so we can make sure this does not get delayed another two years again. >>> >>> Peter >>> >>> >>> On 21 Aug 2025, at 23:18, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>> >>>> >>>> >>>> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org <discuss@lists.openscad.org>>> wrote: >>>> >>>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org <discuss@lists.openscad.org>>> wrote: >>>>>> >>>>> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. >>>>> >>>> >>>> Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. >>>> >>>> We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. >>>>> >>>> >>>> Following this logic, not adding it is simpler. >>>> >>>> What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. >>>> >>>> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? >>>>>> >>>>> >>>>> Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. >>>>> >>>> >>>> "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. >>>> >>>> Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... >>>>> >>>>> I am new here so I might not understand the mores in this project. >>>>> >>>> >>>> I am new as well (though a user for many years). >>>> >>>> However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... >>>>> >>>> >>>> I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. >>>> >>>> It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. >>>> >>>> I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? >>>> >>>> Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. >>>> >>>> The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. >>>>> >>>> >>>> I also think it's important to keep momentum up. >>>> >>>> I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) >>>>> >>>> >>>> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >>>> >>>> - Cory Cross >>>> >>>> >>>>> Peter >>>>> >>>>> >>>>> >>>>> >>>>>> Actually, plists are better because you can get super methods :-) >>>>>> >>>>>> >>>>>> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >>>>>> >>>>>> >>>>>> >>>>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>>>>> >>>>>>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>>>>>> >>>>>>> I like the builder approach and it is one of my drivers for the object work and $this. >>>>>>> >>>>>>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>>>>>> >>>>>>> Peter Kriens >>>>>>> >>>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>> >>>>>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>>>>>>> >>>>>>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>>>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>>>>>> >>>>>>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>>>>>> >>>>>>>> As a practical example, here is a invocation of a threaded module in my code: >>>>>>>> >>>>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>>>> >>>>>>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>>>>>> >>>>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>>>> >>>>>>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>>>>>> >>>>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>>>> >>>>>>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>>>>>> >>>>>>>> module buttress_threaded_nut(kwargs) { >>>>>>>> profile = [ >>>>>>>> [ -1/2, -0.77], >>>>>>>> [ -7/16, -0.75], >>>>>>>> [ 5/16, 0], >>>>>>>> [ 7/16, 0], >>>>>>>> [ 7/16, -0.75], >>>>>>>> [ 1/ 2, -0.77], >>>>>>>> ]; >>>>>>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>>>> } >>>>>>>> >>>>>>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>>>>>> >>>>>>>> The builder pattern could be implemented as so: >>>>>>>> >>>>>>>> // namespace for buttress_threaded_nut_builder >>>>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>>>> function new() = >>>>>>>> let ( profile = [ >>>>>>>> [ -1/2, -0.77], >>>>>>>> [ -7/16, -0.75], >>>>>>>> [ 5/16, 0], >>>>>>>> [ 7/16, 0], >>>>>>>> [ 7/16, -0.75], >>>>>>>> [ 1/ 2, -0.77], >>>>>>>> ]) >>>>>>>> struct(obj)->profile(profile); >>>>>>>> >>>>>>>> so not any more or less difficult to write. What do profile and positioning look like? >>>>>>>> >>>>>>>> // namespace of generic_threaded_rod_builder >>>>>>>> function profile(o is builder_obj, profile) = >>>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>>> struct(o,profile=profile); >>>>>>>> >>>>>>>> // namespace of attachable_builder >>>>>>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>>>>>> assert(/* tests related to the parameters*/) >>>>>>>> let( >>>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>> ) >>>>>>>> struct(o,l); >>>>>>>> >>>>>>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>>>>>> >>>>>>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>>>>>> 2. The `get_radius` function doesn't need its args filled out every time >>>>>>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>>>>>> 4. We can match on the old types and convert to objects as needed >>>>>>>> >>>>>>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>>>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>>>>> >>>>>>>> So how would I write this with `this` as currently proposed? >>>>>>>> >>>>>>>> function buttress_threaded_nut_builder = >>>>>>>> let ( profile = [ >>>>>>>> [ -1/2, -0.77], >>>>>>>> [ -7/16, -0.75], >>>>>>>> [ 5/16, 0], >>>>>>>> [ 7/16, 0], >>>>>>>> [ 7/16, -0.75], >>>>>>>> [ 1/ 2, -0.77], >>>>>>>> ]) >>>>>>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>>>>>> >>>>>>>> // in method list of generic_threaded_rod_builder >>>>>>>> function set_profile(profile) = >>>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>>> object(this,profile=profile); >>>>>>>> >>>>>>>> // in method list of attachable_builder >>>>>>>> function set_positioning(anchor, spin, orient) = >>>>>>>> assert(/* tests related to the parameters*/) >>>>>>>> let( >>>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>> ) >>>>>>>> struct(this,l); >>>>>>>> >>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>> reify_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> >>>>>>>> module reify_threaded_nut(obj) { >>>>>>>> reify_generic_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>>>> >>>>>>>> >>>>>>>> ... >>>>>>>> >>>>>>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>>>>>> >>>>>>>> bosl2_threading = object( >>>>>>>> top_level_constant = 27, >>>>>>>> function buttress_threaded_nut_builder() = >>>>>>>> ... >>>>>>>> ); >>>>>>>> >>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>> bosl2_threading.top_level_constant; // for whatever reason >>>>>>>> reify_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>>>> ------------------------------ >>>>>>>> OpenSCAD mailing list >>>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>>>> >>>>>>> ------------------------------ >>>>>>> OpenSCAD mailing list >>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>>> >>>>>> ------------------------------ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>> >>>>> ------------------------------ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org <discuss-leave@lists.openscad.org>> >>>>> >>>> ------------------------------ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org <discuss-leave@lists.openscad.org>> >>>> >>> ------------------------------ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org >> >> _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
CC
Cory Cross
Sat, Aug 23, 2025 12:48 AM

I've had intermittent Internet access so I've been queuing up messages and didn't see yours first.

I'm not suggesting never adding maps; in fact I've suggested shipping it as implemented (just map) and taking a little more time to figure out how the OO should be done.

On August 22, 2025 8:06:21 PM EDT, Adrian Mariano via Discuss discuss@lists.openscad.org wrote:

Perhaps you're not aware that I'm one of the BOSL2 authors, so when I say
"I implemented" that means it's in BOSL2.

Yes, if I'd seen your message I wouldn't have sent my last one!

Changing struct_val to l just
makes it more confusing to read because the function calls are now very
short.

If it's a convention, to me it's not any different to me than getting used to seeing / instead of ÷ or divide.

My greater point is more that just having maps alone is not going to make everybody's code better. The answer might be just evangelism and documentation, but it also might be taking design lessons from languages with immutable data (like Erlang or Clojure) than copying JS's object system. I'm back to having a computer so I'll type up more code suggestions than prose in the next few days.

  • Cory

It's not about character count exactly---I type very fast---but
about syntactic simplicity and clarity---code readability.  Basically any
time I use these I end up writing foo = struct_val(thing,"foo") to avoid
the syntactic mess that arises from direct access to the structure.  (I am
not a fan of python dicts where you need quotes either.  I return
namedtuples from my functions instead of dicts.)

On Fri, Aug 22, 2025 at 7:58 PM Cory Cross via Discuss <
discuss@lists.openscad.org> wrote:

And, of course it's already done in BOSL2:
https://github.com/BelfrySCAD/BOSL2/wiki/structs.scad

Throw in l=struct_val; to shorten the number of characters to type.

On August 22, 2025 4:17:31 PM EDT, Cory Cross via Discuss <
discuss@lists.openscad.org> wrote:

On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

This discussion is imho a perfect example of bikeshedding https://en.wiktionary.org/wiki/bikeshedding. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs

As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of struct_var.member it's l(struct_var,"member"). That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key.

  • Cory

... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies.

I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours.

I personally would appreciate a time box so we can make sure this does not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org discuss@lists.openscad.org>> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org discuss@lists.openscad.org>> wrote:

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross

Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.

OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org discuss-leave@lists.openscad.org>


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org discuss-leave@lists.openscad.org>


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

I've had intermittent Internet access so I've been queuing up messages and didn't see yours first. I'm not suggesting never adding maps; in fact I've suggested shipping it as implemented (just map) and taking a little more time to figure out how the OO should be done. On August 22, 2025 8:06:21 PM EDT, Adrian Mariano via Discuss <discuss@lists.openscad.org> wrote: >Perhaps you're not aware that I'm one of the BOSL2 authors, so when I say >"I implemented" that means it's in BOSL2. Yes, if I'd seen your message I wouldn't have sent my last one! > Changing struct_val to l just >makes it more confusing to read because the function calls are now very >short. If it's a convention, to me it's not any different to me than getting used to seeing `/` instead of `÷` or `divide`. My greater point is more that just having maps alone is not going to make everybody's code better. The answer might be just evangelism and documentation, but it also might be taking design lessons from languages with immutable data (like Erlang or Clojure) than copying JS's object system. I'm back to having a computer so I'll type up more code suggestions than prose in the next few days. - Cory > It's not about character count exactly---I type very fast---but >about syntactic simplicity and clarity---code readability. Basically any >time I use these I end up writing foo = struct_val(thing,"foo") to avoid >the syntactic mess that arises from direct access to the structure. (I am >not a fan of python dicts where you need quotes either. I return >namedtuples from my functions instead of dicts.) > >On Fri, Aug 22, 2025 at 7:58 PM Cory Cross via Discuss < >discuss@lists.openscad.org> wrote: > >> And, of course it's already done in BOSL2: >> https://github.com/BelfrySCAD/BOSL2/wiki/structs.scad >> >> Throw in `l=struct_val;` to shorten the number of characters to type. >> >> >> On August 22, 2025 4:17:31 PM EDT, Cory Cross via Discuss < >> discuss@lists.openscad.org> wrote: >> >>> >>> >>> On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>> >>>> This discussion is imho a perfect example of bikeshedding <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs >>>> >>> >>> As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of `struct_var.member` it's `l(struct_var,"member")`. That's 17 vs 22 characters or 29% more for a typical case. As a bonus, this userspace method is backwards-compatible and forward-compatible with an optimized implementation which does also allow struct_var.member syntax and allows any type as a key. >>> >>> - Cory >>> >>> >>> >>> ... The enemy of good is 'better' ... This is exactly how the OEP8 effort got derailed more than 2 years ago. At a certain moment everybody gets confused and it dies. >>>> >>>> I suggest you make a working PR so we can play with concrete proposals instead of trying to infer what your sketches mean. If I can use KV pairs and get some concise way to make methods I happily close my PR and support yours. >>>> >>>> I personally would appreciate a time box so we can make sure this does not get delayed another two years again. >>>> >>>> Peter >>>> >>>> >>>> On 21 Aug 2025, at 23:18, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>> >>>>> >>>>> >>>>> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org <discuss@lists.openscad.org>>> wrote: >>>>> >>>>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org <discuss@lists.openscad.org>>> wrote: >>>>>>> >>>>>> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. >>>>>> >>>>> >>>>> Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. >>>>> >>>>> We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. >>>>>> >>>>> >>>>> Following this logic, not adding it is simpler. >>>>> >>>>> What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. >>>>> >>>>> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? >>>>>>> >>>>>> >>>>>> Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. >>>>>> >>>>> >>>>> "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. >>>>> >>>>> Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... >>>>>> >>>>>> I am new here so I might not understand the mores in this project. >>>>>> >>>>> >>>>> I am new as well (though a user for many years). >>>>> >>>>> However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... >>>>>> >>>>> >>>>> I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. >>>>> >>>>> It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. >>>>> >>>>> I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? >>>>> >>>>> Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. >>>>> >>>>> The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. >>>>>> >>>>> >>>>> I also think it's important to keep momentum up. >>>>> >>>>> I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) >>>>>> >>>>> >>>>> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >>>>> >>>>> - Cory Cross >>>>> >>>>> >>>>>> Peter >>>>>> >>>>>> >>>>>> >>>>>> >>>>>>> Actually, plists are better because you can get super methods :-) >>>>>>> >>>>>>> >>>>>>> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >>>>>>> >>>>>>> >>>>>>> >>>>>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>>>>>> >>>>>>>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>>>>>>> >>>>>>>> I like the builder approach and it is one of my drivers for the object work and $this. >>>>>>>> >>>>>>>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>>>>>>> >>>>>>>> Peter Kriens >>>>>>>> >>>>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>>> >>>>>>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>>>>>>>> >>>>>>>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>>>>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>>>>>>> >>>>>>>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>>>>>>> >>>>>>>>> As a practical example, here is a invocation of a threaded module in my code: >>>>>>>>> >>>>>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>>>>> >>>>>>>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>>>>>>> >>>>>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>>>>> >>>>>>>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>>>>>>> >>>>>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>>>>> >>>>>>>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>>>>>>> >>>>>>>>> module buttress_threaded_nut(kwargs) { >>>>>>>>> profile = [ >>>>>>>>> [ -1/2, -0.77], >>>>>>>>> [ -7/16, -0.75], >>>>>>>>> [ 5/16, 0], >>>>>>>>> [ 7/16, 0], >>>>>>>>> [ 7/16, -0.75], >>>>>>>>> [ 1/ 2, -0.77], >>>>>>>>> ]; >>>>>>>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>>>>> } >>>>>>>>> >>>>>>>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>>>>>>> >>>>>>>>> The builder pattern could be implemented as so: >>>>>>>>> >>>>>>>>> // namespace for buttress_threaded_nut_builder >>>>>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>>>>> function new() = >>>>>>>>> let ( profile = [ >>>>>>>>> [ -1/2, -0.77], >>>>>>>>> [ -7/16, -0.75], >>>>>>>>> [ 5/16, 0], >>>>>>>>> [ 7/16, 0], >>>>>>>>> [ 7/16, -0.75], >>>>>>>>> [ 1/ 2, -0.77], >>>>>>>>> ]) >>>>>>>>> struct(obj)->profile(profile); >>>>>>>>> >>>>>>>>> so not any more or less difficult to write. What do profile and positioning look like? >>>>>>>>> >>>>>>>>> // namespace of generic_threaded_rod_builder >>>>>>>>> function profile(o is builder_obj, profile) = >>>>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>>>> struct(o,profile=profile); >>>>>>>>> >>>>>>>>> // namespace of attachable_builder >>>>>>>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>>>>>>> assert(/* tests related to the parameters*/) >>>>>>>>> let( >>>>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>>> ) >>>>>>>>> struct(o,l); >>>>>>>>> >>>>>>>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>>>>>>> >>>>>>>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>>>>>>> 2. The `get_radius` function doesn't need its args filled out every time >>>>>>>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>>>>>>> 4. We can match on the old types and convert to objects as needed >>>>>>>>> >>>>>>>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>>>>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>>>>>> >>>>>>>>> So how would I write this with `this` as currently proposed? >>>>>>>>> >>>>>>>>> function buttress_threaded_nut_builder = >>>>>>>>> let ( profile = [ >>>>>>>>> [ -1/2, -0.77], >>>>>>>>> [ -7/16, -0.75], >>>>>>>>> [ 5/16, 0], >>>>>>>>> [ 7/16, 0], >>>>>>>>> [ 7/16, -0.75], >>>>>>>>> [ 1/ 2, -0.77], >>>>>>>>> ]) >>>>>>>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>>>>>>> >>>>>>>>> // in method list of generic_threaded_rod_builder >>>>>>>>> function set_profile(profile) = >>>>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>>>> object(this,profile=profile); >>>>>>>>> >>>>>>>>> // in method list of attachable_builder >>>>>>>>> function set_positioning(anchor, spin, orient) = >>>>>>>>> assert(/* tests related to the parameters*/) >>>>>>>>> let( >>>>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>>> ) >>>>>>>>> struct(this,l); >>>>>>>>> >>>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>>> reify_threaded_nut(obj); >>>>>>>>> } >>>>>>>>> >>>>>>>>> >>>>>>>>> module reify_threaded_nut(obj) { >>>>>>>>> reify_generic_threaded_nut(obj); >>>>>>>>> } >>>>>>>>> >>>>>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>>>>> >>>>>>>>> >>>>>>>>> ... >>>>>>>>> >>>>>>>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>>>>>>> >>>>>>>>> bosl2_threading = object( >>>>>>>>> top_level_constant = 27, >>>>>>>>> function buttress_threaded_nut_builder() = >>>>>>>>> ... >>>>>>>>> ); >>>>>>>>> >>>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>>> bosl2_threading.top_level_constant; // for whatever reason >>>>>>>>> reify_threaded_nut(obj); >>>>>>>>> } >>>>>>>>> >>>>>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>>>>> ------------------------------ >>>>>>>>> OpenSCAD mailing list >>>>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>>>>> >>>>>>>> ------------------------------ >>>>>>>> OpenSCAD mailing list >>>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>>>> >>>>>>> ------------------------------ >>>>>>> OpenSCAD mailing list >>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>>> >>>>>> ------------------------------ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org <discuss-leave@lists.openscad.org>> >>>>>> >>>>> ------------------------------ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org <discuss-leave@lists.openscad.org>> >>>>> >>>> ------------------------------ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>> >>> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org
RD
Revar Desmera
Sat, Aug 23, 2025 5:04 AM

On Aug 22, 2025, at 1:17 PM, Cory Cross via Discuss discuss@lists.openscad.org wrote:

As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of struct_var.member it's l(struct_var,"member").

We did implement it in BOSL2.  Years ago. It's called struct(). It is very inefficient and a poor substitute for a real associative array.

  • Revar
> On Aug 22, 2025, at 1:17 PM, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: > > As I pointed out before, this isn't true. If you want kv pairs it's trivially implemented with alists. Instead of `struct_var.member` it's `l(struct_var,"member")`. We did implement it in BOSL2. Years ago. It's called `struct()`. It is very inefficient and a poor substitute for a real associative array. - Revar
RD
Revar Desmera
Sat, Aug 23, 2025 5:45 AM

I didn't get much choice in naming the object() function when I wrote it. The is_object() function had already existed for a year or so at the time. Also, the textmetrics() and fontmetrics() calls already had their return type called objects. Otherwise I would have called the type a dictionary, and the function dict().

Really, object() isn't something that a regular user is going to use often, much less the $this variable, or module literals. Simple projects do not need object orientation.  This is a set of features targeted for library writers, or large code bases, or for complicated algorithms.  The vast majority of users will only need to know the dot (.) syntax to use it, preserving the apparent simplicity of the language.

bosl = use("BOSL3/std.scad");
bosl.Cuboid([30,40,50])
.Chamfer(5, angle=60)
.Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT)
.Show();

Note that builder design pattern makes the code much more verbose, so that seems like an awful example.

  • Revar
I didn't get much choice in naming the `object()` function when I wrote it. The `is_object()` function had already existed for a year or so at the time. Also, the `textmetrics()` and `fontmetrics()` calls already had their return type called objects. Otherwise I would have called the type a dictionary, and the function `dict()`. Really, `object()` isn't something that a regular user is going to use often, much less the `$this` variable, or module literals. Simple projects do not need object orientation. This is a set of features targeted for library writers, or large code bases, or for complicated algorithms. The vast majority of users will only need to know the dot (.) syntax to use it, preserving the apparent simplicity of the language. bosl = use("BOSL3/std.scad"); bosl.Cuboid([30,40,50]) .Chamfer(5, angle=60) .Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT) .Show(); Note that builder design pattern makes the code much more verbose, so that seems like an awful example. - Revar
L
larry
Sat, Aug 23, 2025 6:03 AM

On Fri, 2025-08-22 at 22:45 -0700, Revar Desmera via Discuss wrote:

bosl = use("BOSL3/std.scad");
bosl.Cuboid([30,40,50])
    .Chamfer(5, angle=60)
    .Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT)
    .Show();

Hmm... should that work in 2025.08.19.ai26698 ?

On Fri, 2025-08-22 at 22:45 -0700, Revar Desmera via Discuss wrote: > > > > bosl = use("BOSL3/std.scad"); > > > > bosl.Cuboid([30,40,50]) > > > >     .Chamfer(5, angle=60) > > > >     .Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT) > > > >     .Show(); Hmm... should that work in 2025.08.19.ai26698 ?
PK
Peter Kriens
Sat, Aug 23, 2025 12:59 PM

Note that builder design pattern makes the code much more verbose, so that seems like an awful example.

I agree that the builder pattern has much less oomph in Openscad than in Java and other such languages. The named parameters with defaults in a function/module call handle many of the cases where one needs a builder in Java . I noticed in this work that many intuitions based on 30 year Java tend to not work well in OpenSCAD. That said, builders have their advantages in some cases.

My favorite use case for object() is a prism object (a solid between two paths). I am addicted to rounded_prism in BOSL2. However many of my shapes are hollow and closed/open on the bottom/top and then you need to calculate different variations of the same geometry. Hiding the detailed operations behind an object facade really cleans up the code and makes it a lot more readable. When I look at BOSL2 it will make a tremendous difference for the readability if something like the geom and vnf structures were behind an object facade.

I already had a home made object system a la Cory using strings for the method names and fields, using object() now makes it much more readable as Adrian also indicated because you can use the well known dot notation. The lack of $this however still requires a lot of ugly repetitive boiler plate cruft. (Which makes me completely flummoxed to the opposition to this !)

 Peter

Sent from my iPad

On 23 Aug 2025, at 07:46, Revar Desmera via Discuss discuss@lists.openscad.org wrote:

I didn't get much choice in naming the object() function when I wrote it. The is_object() function had already existed for a year or so at the time. Also, the textmetrics() and fontmetrics() calls already had their return type called objects. Otherwise I would have called the type a dictionary, and the function dict().

Really, object() isn't something that a regular user is going to use often, much less the $this variable, or module literals. Simple projects do not need object orientation.  This is a set of features targeted for library writers, or large code bases, or for complicated algorithms.  The vast majority of users will only need to know the dot (.) syntax to use it, preserving the apparent simplicity of the language.

bosl = use("BOSL3/std.scad");
bosl.Cuboid([30,40,50])
.Chamfer(5, angle=60)
.Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT)
.Show();

Note that builder design pattern makes the code much more verbose, so that seems like an awful example.

  • Revar

OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

> Note that builder design pattern makes the code much more verbose, so that seems like an awful example. I agree that the builder pattern has much less oomph in Openscad than in Java and other such languages. The named parameters with defaults in a function/module call handle many of the cases where one needs a builder in Java . I noticed in this work that many intuitions based on 30 year Java tend to not work well in OpenSCAD. That said, builders have their advantages in some cases. My favorite use case for object() is a prism object (a solid between two paths). I am addicted to rounded_prism in BOSL2. However many of my shapes are hollow and closed/open on the bottom/top and then you need to calculate different variations of the same geometry. Hiding the detailed operations behind an object facade really cleans up the code and makes it a lot more readable. When I look at BOSL2 it will make a tremendous difference for the readability if something like the geom and vnf structures were behind an object facade. I already had a home made object system a la Cory using strings for the method names and fields, using object() now makes it much more readable as Adrian also indicated because you can use the well known dot notation. The lack of $this however still requires a lot of ugly repetitive boiler plate cruft. (Which makes me completely flummoxed to the opposition to this !) Peter Sent from my iPad > On 23 Aug 2025, at 07:46, Revar Desmera via Discuss <discuss@lists.openscad.org> wrote: > > I didn't get much choice in naming the `object()` function when I wrote it. The `is_object()` function had already existed for a year or so at the time. Also, the `textmetrics()` and `fontmetrics()` calls already had their return type called objects. Otherwise I would have called the type a dictionary, and the function `dict()`. > > Really, `object()` isn't something that a regular user is going to use often, much less the `$this` variable, or module literals. Simple projects do not need object orientation. This is a set of features targeted for library writers, or large code bases, or for complicated algorithms. The vast majority of users will only need to know the dot (.) syntax to use it, preserving the apparent simplicity of the language. > > bosl = use("BOSL3/std.scad"); > bosl.Cuboid([30,40,50]) > .Chamfer(5, angle=60) > .Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT) > .Show(); > > Note that builder design pattern makes the code much more verbose, so that seems like an awful example. > > - Revar > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
PK
Peter Kriens
Sat, Aug 23, 2025 1:47 PM

Then make a proposal.

I don’t give a rats ass how things are called because a keyword in a language is always mnemonic, never definitional. (I managed spec writing for 20 years of my life.) If you think the name is so all important propose ONE name and then I can sit back and watch another few weeks of bikeshedding here to get consensus. I’ll even rename the built in function for you once that happens!

However, for me it is absolutely crucial to have a mechanism for methods: functions that can access their owning map, key-value pairs, associative array, dictionary, symbol table, struct, record, lookup table, registry, index, function, and dare I say it, object,foobars, etc. because it helps scoping the names.

Today, libraries need to declare global names for functions and variables today, methods allow them to keep the scope local. Today you therefore need to come up with unwieldy names to make them globally unique. For example, BOSL had to invent new names for cube (cuboid) because the nice names were taken. With foobars they can have their own scope. I strongly believe it also makes complex code much more readable.

Since you have derailed that as well, any proposal for this?

Sent from my iPad

On 22 Aug 2025, at 18:06, Cory Cross via Discuss discuss@lists.openscad.org wrote:



On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:
This discussion is imho a perfect example of bikeshedding https://en.wiktionary.org/wiki/bikeshedding. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs

Let's stop trying to make them objects and just call them dictionary, map, associative array, or any of the many other names for this: https://en.m.wikipedia.org/wiki/Associative_array

The words you use carry connotations and have denotations that users will use to understand what it is. If you offer to pay 100 Euros and show up with a 1-Euro bill, the other party is going to be upset and confused even if you explain you don't pronounce the "ents" of "cents".

  • Cory

I personally would appreciate a time box so we can make sure this does not get delayed another two years again.

Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy.

Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer?

Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross
Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the object work and $this.

BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ...

Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all.  Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function?
If the former, I don't understand what it means.  If the latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here, optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
];
generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
assert(is_list(profile)) // And other tests independent of other values
struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(o,l);

Okay, not in love with the repetition in there. But there are some improvements here:

  1. Some validation can now stop cluttering the top of so many functions/modules.
  2. The get_radius function doesn't need its args filled out every time
  3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
let ( profile = [
[  -1/2, -0.77],
[ -7/16, -0.75],
[  5/16,  0],
[  7/16,  0],
[  7/16, -0.75],
[  1/ 2, -0.77],
])
object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
assert(is_list(profile)) // And other tests independent of other values
object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
assert(/* tests related to the parameters*/)
let(
l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
is_undef(spin) ? [] : [["spin", spin]],
is_undef(orient) ? [] : [["orient", orient]])
)
struct(this,l);

module reify_buttress_threaded_nut(obj) {
reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
top_level_constant = 27,
function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
bosl2_threading.top_level_constant; // for whatever reason
reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Then make a proposal. I don’t give a rats ass how things are called because a keyword in a language is always mnemonic, never definitional. (I managed spec writing for 20 years of my life.) If you think the name is so all important propose ONE name and then I can sit back and watch another few weeks of bikeshedding here to get consensus. I’ll even rename the built in function for you once that happens! However, for me it is absolutely crucial to have a mechanism for _methods_: functions that can access their owning map, key-value pairs, associative array, dictionary, symbol table, struct, record, lookup table, registry, index, function, and dare I say it, object,foobars, etc. because it helps scoping the names. Today, libraries need to declare global names for functions and variables today, methods allow them to keep the scope local. Today you therefore need to come up with unwieldy names to make them globally unique. For example, BOSL had to invent new names for cube (cuboid) because the nice names were taken. With foobars they can have their own scope. I strongly believe it also makes complex code much more readable. Since you have derailed that as well, any proposal for this? Sent from my iPad > On 22 Aug 2025, at 18:06, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: > >  > >> On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >> This discussion is imho a perfect example of bikeshedding <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this forever on a forum like this because we're all highly experienced & opinionated but in the mean time OpenSCAD has no KV pairs > > Let's stop trying to make them objects and just call them dictionary, map, associative array, or any of the many other names for this: https://en.m.wikipedia.org/wiki/Associative_array > > The words you use carry connotations and have denotations that users will use to understand what it is. If you offer to pay 100 Euros and show up with a 1-Euro bill, the other party is going to be upset and confused even if you explain you don't pronounce the "ents" of "cents". > > - Cory > > >> I personally would appreciate a time box so we can make sure this does not get delayed another two years again. > > > >> >> Peter >> >> >>>> On 21 Aug 2025, at 23:18, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>> >>> >>> >>> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>> Objects do NOT have parents so I am not sure what you're talking about? An object is a flat set of key-value pairs. There is no hierarchy. >>> >>> Then they shouldn't be called objects. At least 99% of people who have or will use OpenSCAD will associate objects with the mainstream object-oriented languages which all put inheritance front-and-center. It's day-one "learning Python" material. I don't think there's a single language with a "this" keyword that doesn't have inheritance. You're setting up people for confusion. >>> >>>> We are very intentionally not implementing a full blow OO system to keep OpenSCAD as simple as possible, but not simpler. >>> >>> Following this logic, not adding it is simpler. >>> >>> What do you want to be simple: writing SCAD or the implementation of OpenSCAD? There's often (but not always) a trade-off. Brainfuck is very simple to implement. You can solve some very complicated analyses in a single line of Mathematica. >>> >>>>> So we already have the power to do builders, it's just slightly uglier and slower. My question is while settle for "this" when generic functions are even nicer? >>>> >>>> Slightly??? We must live in another universe. There are few types of code I'd like to write less than this kind of boiler plate code. I wrote several of these 'OO' systems but they were quite ugly. >>> >>> "this" refers to the proposal to add a "this" keyword. But what I wrote shows that, with a couple helper functions, the proposed object() does not result in substantially simpler code and by the maxim "keep OpenSCAD as simple as possible, but not simpler", shouldn't be added. >>> >>>> Then you threw in an a-bom ... and another one. I am getting a bit desperate and feel this is going way off the track and taking way too much of my time ... >>>> >>>> I am new here so I might not understand the mores in this project. >>> >>> I am new as well (though a user for many years). >>> >>>> However, in other projects I am used that if you want to derail a PR you make a fully working counter PR so people can play with the proposals and compare. I find that you're now just dropping disruptive ideas ... >>> >>> I've not found any other discussion of OEP8 and wasn't active at the time anyway. I am discussing now because now is when I'm here. >>> >>> It's my impression the "this" keyword is just being added because people are unfamiliar with other systems. If SCAD was a hybrid procedural/OO lisp with mutable values like JavaScript, then it'd be fine to copy their semantics. But it's not and I think you're going down the wrong road. >>> >>> I'm trying to prove it by picking bosl2 and showing how I'd refactor it using the proposed "this" approach or the one I'm proposing, because ultimately what we want is what makes it easier and faster to write correct code, right? >>> >>> Everything I've proposed is quite easy to implement and I'll be happy to do it and/or collaborate on it. >>> >>>> The danger here is that we spend a lot of time talking back and forth and then nothing happens again because everything got so complicated. I think this partly happened with OEP8 and that spent a lot of time in discussion. There are very good, some crucial, ideas in that PR that has been idling since 2023. >>> >>> I also think it's important to keep momentum up. >>> >>>> I know I can be a bit blunt but you can blame it on my Dutch citizenship ;-) >>> >>> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >>> >>> - Cory Cross >>> >>>> >>>> Peter >>>> >>>> >>>> >>>>> >>>>> Actually, plists are better because you can get super methods :-) >>>>> >>>>> >>>>> I'm on my phone composing this without Internet access, so please forgive the formatting and mild syntax errors. >>>>> >>>>> >>>>> >>>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss <discuss@lists.openscad.org> wrote: >>>>>> You flabbergasted me with the completely different direction/syntax you took but then at the end I saw that you came to the conclusion you could do all this also with 'this' and `object`? Not good for my blood pressure! :-) >>>>>> >>>>>> I like the builder approach and it is one of my drivers for the object work and $this. >>>>>> >>>>>> BTW, notice that OEP8 also proposed to have modules as expression. I am currently working on a PR for this. This will allow builders to also call modules, have modules as variables, and hopefully modules as methods when we can finally close [the $]this discussion ... >>>>>> >>>>>> Peter Kriens >>>>>> >>>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss <discuss@lists.openscad.org> wrote: >>>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>>> bosl2::threading::nut_builder::new(required, args, here)->optional_generic_arg(its_value)->reify(); >>>>>>>> I'm very sympathetic to the desire to reduce repetition in argument handling, but I'm not understanding what that means at all. Partly that's presentation; is this intended to be how the library would say something, or how the caller would invoke the function? >>>>>>>> If the former, I don't understand what it means. If the latter, are you seriously suggesting this as a replacement for >>>>>>>> threaded_nut(required, args, here, optional_generic_arc=its_value); >>>>>>> I am suggesting it as a replacement for the latter; not because it's better for the user, but because it's better for the maintainers and not worse for the users. (I would assume we'd add `using bosl2::threading` to shorten names, at some point). >>>>>>> >>>>>>> As a practical example, here is a invocation of a threaded module in my code: >>>>>>> >>>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>>> >>>>>>> here's how I would do it with the builder pattern and the suggested OO approach: >>>>>>> >>>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>>> >>>>>>> You don't have to use the builder pattern. You could choose to use objects as a replacement for Python's **kwargs: >>>>>>> >>>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>>> >>>>>>> In this case, buttress_threaded_nut's implementation would change from 50 lines to 11: >>>>>>> >>>>>>> module buttress_threaded_nut(kwargs) { >>>>>>> profile = [ >>>>>>> [ -1/2, -0.77], >>>>>>> [ -7/16, -0.75], >>>>>>> [ 5/16, 0], >>>>>>> [ 7/16, 0], >>>>>>> [ 7/16, -0.75], >>>>>>> [ 1/ 2, -0.77], >>>>>>> ]; >>>>>>> generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>>> } >>>>>>> >>>>>>> Of course, this isn't OO and makes it harder to detect argument name typos and such. >>>>>>> >>>>>>> The builder pattern could be implemented as so: >>>>>>> >>>>>>> // namespace for buttress_threaded_nut_builder >>>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>>> function new() = >>>>>>> let ( profile = [ >>>>>>> [ -1/2, -0.77], >>>>>>> [ -7/16, -0.75], >>>>>>> [ 5/16, 0], >>>>>>> [ 7/16, 0], >>>>>>> [ 7/16, -0.75], >>>>>>> [ 1/ 2, -0.77], >>>>>>> ]) >>>>>>> struct(obj)->profile(profile); >>>>>>> >>>>>>> so not any more or less difficult to write. What do profile and positioning look like? >>>>>>> >>>>>>> // namespace of generic_threaded_rod_builder >>>>>>> function profile(o is builder_obj, profile) = >>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>> struct(o,profile=profile); >>>>>>> >>>>>>> // namespace of attachable_builder >>>>>>> function positioning(o is attachable_builder_obj, anchor, spin, orient) = >>>>>>> assert(/* tests related to the parameters*/) >>>>>>> let( >>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>> ) >>>>>>> struct(o,l); >>>>>>> >>>>>>> Okay, not in love with the repetition in there. But there are some improvements here: >>>>>>> >>>>>>> 1. Some validation can now stop cluttering the top of so many functions/modules. >>>>>>> 2. The `get_radius` function doesn't need its args filled out every time >>>>>>> 3. We're reusing attachable instead of needing to redundantly pass it so many args every single time and in every function and module signature. >>>>>>> 4. We can match on the old types and convert to objects as needed >>>>>>> >>>>>>> Unsolved issue: why would -> method invocation not look at the namespace of positioning? I didn't intend it to, but it would do the wrong thing as written; only the calls in the new() methods should add the namespace to the method lookup. This might be best as `object` vs `struct` keywords. >>>>>>> Maybe attachable should be a mixin instead of in the class hierarchy. >>>>>>> >>>>>>> So how would I write this with `this` as currently proposed? >>>>>>> >>>>>>> function buttress_threaded_nut_builder = >>>>>>> let ( profile = [ >>>>>>> [ -1/2, -0.77], >>>>>>> [ -7/16, -0.75], >>>>>>> [ 5/16, 0], >>>>>>> [ 7/16, 0], >>>>>>> [ 7/16, -0.75], >>>>>>> [ 1/ 2, -0.77], >>>>>>> ]) >>>>>>> object(new_generic_threaded_rod_builder().set_profile(profile), /* all methods on buttress_threaded_nut_builder must be defined here */); >>>>>>> >>>>>>> // in method list of generic_threaded_rod_builder >>>>>>> function set_profile(profile) = >>>>>>> assert(is_list(profile)) // And other tests independent of other values >>>>>>> object(this,profile=profile); >>>>>>> >>>>>>> // in method list of attachable_builder >>>>>>> function set_positioning(anchor, spin, orient) = >>>>>>> assert(/* tests related to the parameters*/) >>>>>>> let( >>>>>>> l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>> is_undef(spin) ? [] : [["spin", spin]], >>>>>>> is_undef(orient) ? [] : [["orient", orient]]) >>>>>>> ) >>>>>>> struct(this,l); >>>>>>> >>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>> reify_threaded_nut(obj); >>>>>>> } >>>>>>> >>>>>>> >>>>>>> module reify_threaded_nut(obj) { >>>>>>> reify_generic_threaded_nut(obj); >>>>>>> } >>>>>>> >>>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>>> >>>>>>> >>>>>>> ... >>>>>>> >>>>>>> Less different than I thought. Cascading explicit reify modules are annoying. For this usage the inability to write your own generic methods dispatching on other object types does not hinder anything. The shared namespace means methods and values must have unique names. I think this is supposed to solve name conflicts by having you only need one unique name per file and you put all your constants in there? And methods, I guess, so at the top of f.ex. bosl2/threading.scad there would be: >>>>>>> >>>>>>> bosl2_threading = object( >>>>>>> top_level_constant = 27, >>>>>>> function buttress_threaded_nut_builder() = >>>>>>> ... >>>>>>> ); >>>>>>> >>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>> bosl2_threading.top_level_constant; // for whatever reason >>>>>>> reify_threaded_nut(obj); >>>>>>> } >>>>>>> >>>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>>> _______________________________________________ >>>>>>> OpenSCAD mailing list >>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>> >>>>>> _______________________________________________ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
JB
Jon Bondy
Sat, Aug 23, 2025 2:02 PM

Excellent.  Requirements!

It is absolutely crucial to have a mechanism for methods: functions
that can access their owning map, key-value pairs, associative array,
dictionary, symbol table, struct, record, lookup table, registry, index,
function, and dare I say it, object,foobars, etc. /because it helps
scoping the names/.

Today, libraries need to declare global names for functions and
variables today, methods allow them to keep the scope local. Today you
therefore need to come up with unwieldy names to make them globally
unique. For example, BOSL had to invent new names for cube (cuboid)
because the nice names were taken. With foobars they can have their own
scope. I strongly believe it also makes complex code much more readable.

On 8/23/2025 9:47 AM, Peter Kriens via Discuss wrote:

Then make a proposal.

I don’t give a rats ass how things are called because a keyword in a
language is always /mnemonic/, never /definitional/. (I managed spec
writing for 20 years of my life.) If you think the name is so all
important propose ONE name and then I can sit back and watch another
few weeks of bikeshedding here to get consensus. I’ll even rename the
built in function for you once that happens!

However, for me it is absolutely crucial to have a mechanism for
methods: functions that can access their owning map, key-value
pairs, associative array, dictionary, symbol table, struct, record,
lookup table, registry, index, function, and dare I say it,
object,foobars, etc. /because it helps scoping the names/.

Today, libraries need to declare global names for functions and
variables today, methods allow them to keep the scope local. Today you
therefore need to come up with unwieldy names to make them globally
unique. For example, BOSL had to invent new names for cube (cuboid)
because the nice names were taken. With foobars they can have their
own scope. I strongly believe it also makes complex code much more
readable.

Since you have derailed that as well, any proposal for this?

Sent from my iPad

On 22 Aug 2025, at 18:06, Cory Cross via Discuss
discuss@lists.openscad.org wrote:



On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss
discuss@lists.openscad.org wrote:

This discussion is imho a perfect example of bikeshedding
https://en.wiktionary.org/wiki/bikeshedding. We can discuss this
forever on a forum like this because we're all highly experienced &
opinionated but in the mean time OpenSCAD has no KV pairs

Let's stop trying to make them objects and just call them dictionary,
map, associative array, or any of the many other names for this:
https://en.m.wikipedia.org/wiki/Associative_array

The words you use carry connotations and have denotations that users
will use to understand what it is. If you offer to pay 100 Euros and
show up with a 1-Euro bill, the other party is going to be upset and
confused even if you explain you don't pronounce the "ents" of "cents".

  • Cory

I personally would appreciate a time box so we can make sure this
does not get delayed another two years again.

   Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss
discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss
<discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss
<discuss@lists.openscad.org mailto:discuss@lists.openscad.org>
wrote:

Objects do NOT have parents so I am not sure what you're talking
about? An object is a flat set of key-value pairs. There is no
hierarchy.

Then they shouldn't be called objects. At least 99% of people who
have or will use OpenSCAD will associate objects with the
mainstream object-oriented languages which all put inheritance
front-and-center. It's day-one "learning Python" material. I don't
think there's a single language with a "this" keyword that doesn't
have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system
to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation
of OpenSCAD? There's often (but not always) a trade-off. Brainfuck
is very simple to implement. You can solve some very complicated
analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly
uglier and slower. My question is while settle for "this" when
generic functions are even nicer?

Slightly??? We must live in another universe. There are few types
of code I'd like to write less than this kind of boiler plate
code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I
wrote shows that, with a couple helper functions, the proposed
object() does not result in substantially simpler code and by the
maxim "keep OpenSCAD as simple as possible, but not simpler",
shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit
desperate and feel this is going way off the track and taking way
too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a
PR you make a fully working counter PR so people can play with the
proposals and compare. I find that you're now just dropping
disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at
the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because
people are unfamiliar with other systems. If SCAD was a hybrid
procedural/OO lisp with mutable values like JavaScript, then it'd
be fine to copy their semantics. But it's not and I think you're
going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd
refactor it using the proposed "this" approach or the one I'm
proposing, because ultimately what we want is what makes it easier
and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be
happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and
forth and then nothing happens again because everything got so
complicated. I think this partly happened with OEP8 and that spent
a lot of time in discussion. There are very good, some crucial,
ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch
citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross

   Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please
forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss
discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different
direction/syntax you took but then at the end I saw that you
came to the conclusion you could do all this also with 'this'
and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the
object work and $this.

BTW, notice that OEP8 also proposed to have modules as
expression. I am currently working on a PR for this. This will
allow builders to also call modules, have modules as variables,
and hopefully modules as methods when we can finally close [the
$]this discussion ...

   Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss
discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args,
here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in
argument handling, but I'm not understanding what that means
at all.  Partly that's presentation; is this intended to be
how the library would say something, or how the caller would
invoke the function?
If the former, I don't understand what it means.  If the
latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here,
optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because
it's better for the user, but because it's better for the
maintainers and not worse for the users. (I would assume we'd
add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded
module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the
suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to
use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would
change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
 profile = [
     [  -1/2, -0.77],
     [ -7/16, -0.75],
     [  5/16,  0],
     [  7/16,  0],
     [  7/16, -0.75],
     [  1/ 2, -0.77],
 ];
 generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument
name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
 let ( profile = [
     [  -1/2, -0.77],
     [ -7/16, -0.75],
     [  5/16,  0],
     [  7/16,  0],
     [  7/16, -0.75],
     [  1/ 2, -0.77],
 ])
 struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and
positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
 assert(is_list(profile)) // And other tests independent of
other values
 struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin,
orient) =
 assert(/* tests related to the parameters*/)
 let(
     l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
        is_undef(spin) ? [] : [["spin", spin]],
        is_undef(orient) ? [] : [["orient", orient]])
     )
 struct(o,l);

Okay, not in love with the repetition in there. But there are
some improvements here:

  1. Some validation can now stop cluttering the top of so many
    functions/modules.
  2. The get_radius function doesn't need its args filled out
    every time
  3. We're reusing attachable instead of needing to redundantly
    pass it so many args every single time and in every function
    and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the
namespace of positioning? I didn't intend it to, but it would
do the wrong thing as written; only the calls in the new()
methods should add the namespace to the method lookup. This
might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class
hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
 let ( profile = [
     [  -1/2, -0.77],
     [ -7/16, -0.75],
     [  5/16,  0],
     [  7/16,  0],
     [  7/16, -0.75],
     [  1/ 2, -0.77],
 ])
 object(new_generic_threaded_rod_builder().set_profile(profile),
/* all methods on buttress_threaded_nut_builder must be defined
here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
 assert(is_list(profile)) // And other tests independent of
other values
 object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
 assert(/* tests related to the parameters*/)
 let(
     l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
        is_undef(spin) ? [] : [["spin", spin]],
        is_undef(orient) ? [] : [["orient", orient]])
     )
 struct(this,l);

module reify_buttress_threaded_nut(obj) {
 reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
 reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules
are annoying. For this usage the inability to write your own
generic methods dispatching on other object types does not
hinder anything. The shared namespace means methods and values
must have unique names. I think this is supposed to solve name
conflicts by having you only need one unique name per file and
you put all your constants in there? And methods, I guess, so
at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
 top_level_constant = 27,
 function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
 bosl2_threading.top_level_constant; // for whatever reason
 reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


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

Excellent.  Requirements! It is absolutely crucial to have a mechanism for _methods_: functions that can access their owning map, key-value pairs, associative array, dictionary, symbol table, struct, record, lookup table, registry, index, function, and dare I say it, object,foobars, etc. /because it helps scoping the names/. Today, libraries need to declare global names for functions and variables today, methods allow them to keep the scope local. Today you therefore need to come up with unwieldy names to make them globally unique. For example, BOSL had to invent new names for cube (cuboid) because the nice names were taken. With foobars they can have their own scope. I strongly believe it also makes complex code much more readable. On 8/23/2025 9:47 AM, Peter Kriens via Discuss wrote: > Then make a proposal. > > I don’t give a rats ass how things are called because a keyword in a > language is always /mnemonic/, never /definitional/. (I managed spec > writing for 20 years of my life.) If you think the name is so all > important propose ONE name and then I can sit back and watch another > few weeks of bikeshedding here to get consensus. I’ll even rename the > built in function for you once that happens! > > However, for me it is absolutely crucial to have a mechanism for > _methods_: functions that can access their owning map, key-value > pairs, associative array, dictionary, symbol table, struct, record, > lookup table, registry, index, function, and dare I say it, > object,foobars, etc. /because it helps scoping the names/. > > Today, libraries need to declare global names for functions and > variables today, methods allow them to keep the scope local. Today you > therefore need to come up with unwieldy names to make them globally > unique. For example, BOSL had to invent new names for cube (cuboid) > because the nice names were taken. With foobars they can have their > own scope. I strongly believe it also makes complex code much more > readable. > > Since you have derailed that as well, any proposal for this? > > > Sent from my iPad > >> On 22 Aug 2025, at 18:06, Cory Cross via Discuss >> <discuss@lists.openscad.org> wrote: >> >>  >> >> On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss >> <discuss@lists.openscad.org> wrote: >>> This discussion is imho a perfect example of bikeshedding >>> <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this >>> forever on a forum like this because we're all highly experienced & >>> opinionated but in the mean time OpenSCAD has no KV pairs >> >> Let's stop trying to make them objects and just call them dictionary, >> map, associative array, or any of the many other names for this: >> https://en.m.wikipedia.org/wiki/Associative_array >> >> The words you use carry connotations and have denotations that users >> will use to understand what it is. If you offer to pay 100 Euros and >> show up with a 1-Euro bill, the other party is going to be upset and >> confused even if you explain you don't pronounce the "ents" of "cents". >> >> - Cory >> >> >>> I personally would appreciate a time box so we can make sure this >>> does not get delayed another two years again. >> >> >> >>> >>>    Peter >>> >>> >>>> On 21 Aug 2025, at 23:18, Cory Cross via Discuss >>>> <discuss@lists.openscad.org> wrote: >>>> >>>> >>>> >>>> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss >>>> <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss >>>>>> <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> >>>>>> wrote: >>>>> Objects do NOT have parents so I am not sure what you're talking >>>>> about? An object is a flat set of key-value pairs. There is no >>>>> hierarchy. >>>> >>>> Then they shouldn't be called objects. At least 99% of people who >>>> have or will use OpenSCAD will associate objects with the >>>> mainstream object-oriented languages which all put inheritance >>>> front-and-center. It's day-one "learning Python" material. I don't >>>> think there's a single language with a "this" keyword that doesn't >>>> have inheritance. You're setting up people for confusion. >>>> >>>>> We are very intentionally not implementing a full blow OO system >>>>> to keep OpenSCAD as simple as possible, but not simpler. >>>> >>>> Following this logic, not adding it is simpler. >>>> >>>> What do you want to be simple: writing SCAD or the implementation >>>> of OpenSCAD? There's often (but not always) a trade-off. Brainfuck >>>> is very simple to implement. You can solve some very complicated >>>> analyses in a single line of Mathematica. >>>> >>>>>> So we already have the power to do builders, it's just slightly >>>>>> uglier and slower. My question is while settle for "this" when >>>>>> generic functions are even nicer? >>>>> >>>>> Slightly??? We must live in another universe. There are few types >>>>> of code I'd like to write less than this kind of boiler plate >>>>> code. I wrote several of these 'OO' systems but they were quite ugly. >>>> >>>> "this" refers to the proposal to add a "this" keyword. But what I >>>> wrote shows that, with a couple helper functions, the proposed >>>> object() does not result in substantially simpler code and by the >>>> maxim "keep OpenSCAD as simple as possible, but not simpler", >>>> shouldn't be added. >>>> >>>>> Then you threw in an a-bom ... and another one. I am getting a bit >>>>> desperate and feel this is going way off the track and taking way >>>>> too much of my time ... >>>>> >>>>> I am new here so I might not understand the mores in this project. >>>> >>>> I am new as well (though a user for many years). >>>> >>>>> However, in other projects I am used that if you want to derail a >>>>> PR you make a fully working counter PR so people can play with the >>>>> proposals and compare. I find that you're now just dropping >>>>> disruptive ideas ... >>>> >>>> I've not found any other discussion of OEP8 and wasn't active at >>>> the time anyway. I am discussing now because now is when I'm here. >>>> >>>> It's my impression the "this" keyword is just being added because >>>> people are unfamiliar with other systems. If SCAD was a hybrid >>>> procedural/OO lisp with mutable values like JavaScript, then it'd >>>> be fine to copy their semantics. But it's not and I think you're >>>> going down the wrong road. >>>> >>>> I'm trying to prove it by picking bosl2 and showing how I'd >>>> refactor it using the proposed "this" approach or the one I'm >>>> proposing, because ultimately what we want is what makes it easier >>>> and faster to write correct code, right? >>>> >>>> Everything I've proposed is quite easy to implement and I'll be >>>> happy to do it and/or collaborate on it. >>>> >>>>> The danger here is that we spend a lot of time talking back and >>>>> forth and then nothing happens again because everything got so >>>>> complicated. I think this partly happened with OEP8 and that spent >>>>> a lot of time in discussion. There are very good, some crucial, >>>>> ideas in that PR that has been idling since 2023. >>>> >>>> I also think it's important to keep momentum up. >>>> >>>>> I know I can be a bit blunt but you can blame it on my Dutch >>>>> citizenship ;-) >>>> >>>> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >>>> >>>> - Cory Cross >>>> >>>>> >>>>>    Peter >>>>> >>>>> >>>>> >>>>>> >>>>>> Actually, plists are better because you can get super methods :-) >>>>>> >>>>>> >>>>>> I'm on my phone composing this without Internet access, so please >>>>>> forgive the formatting and mild syntax errors. >>>>>> >>>>>> >>>>>> >>>>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss >>>>>> <discuss@lists.openscad.org> wrote: >>>>>>> You flabbergasted me with the completely different >>>>>>> direction/syntax you took but then at the end I saw that you >>>>>>> came to the conclusion you could do all this also with 'this' >>>>>>> and `object`? Not good for my blood pressure! :-) >>>>>>> >>>>>>> I like the builder approach and it is one of my drivers for the >>>>>>> object work and $this. >>>>>>> >>>>>>> BTW, notice that OEP8 also proposed to have modules as >>>>>>> expression. I am currently working on a PR for this. This will >>>>>>> allow builders to also call modules, have modules as variables, >>>>>>> and hopefully modules as methods when we can finally close [the >>>>>>> $]this discussion ... >>>>>>> >>>>>>>    Peter Kriens >>>>>>> >>>>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss >>>>>>>> <discuss@lists.openscad.org> wrote: >>>>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>>>> bosl2::threading::nut_builder::new(required, args, >>>>>>>>>> here)->optional_generic_arg(its_value)->reify(); >>>>>>>>> I'm very sympathetic to the desire to reduce repetition in >>>>>>>>> argument handling, but I'm not understanding what that means >>>>>>>>> at all.  Partly that's presentation; is this intended to be >>>>>>>>> how the library would say something, or how the caller would >>>>>>>>> invoke the function? >>>>>>>>> If the former, I don't understand what it means.  If the >>>>>>>>> latter, are you seriously suggesting this as a replacement for >>>>>>>>> threaded_nut(required, args, here, >>>>>>>>> optional_generic_arc=its_value); >>>>>>>> I am suggesting it as a replacement for the latter; not because >>>>>>>> it's better for the user, but because it's better for the >>>>>>>> maintainers and not worse for the users. (I would assume we'd >>>>>>>> add `using bosl2::threading` to shorten names, at some point). >>>>>>>> >>>>>>>> As a practical example, here is a invocation of a threaded >>>>>>>> module in my code: >>>>>>>> >>>>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>>>> >>>>>>>> here's how I would do it with the builder pattern and the >>>>>>>> suggested OO approach: >>>>>>>> >>>>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>>>> >>>>>>>> You don't have to use the builder pattern. You could choose to >>>>>>>> use objects as a replacement for Python's **kwargs: >>>>>>>> >>>>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>>>> >>>>>>>> In this case, buttress_threaded_nut's implementation would >>>>>>>> change from 50 lines to 11: >>>>>>>> >>>>>>>> module buttress_threaded_nut(kwargs) { >>>>>>>>  profile = [ >>>>>>>>      [  -1/2, -0.77], >>>>>>>>      [ -7/16, -0.75], >>>>>>>>      [  5/16,  0], >>>>>>>>      [  7/16,  0], >>>>>>>>      [  7/16, -0.75], >>>>>>>>      [  1/ 2, -0.77], >>>>>>>>  ]; >>>>>>>>  generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>>>> } >>>>>>>> >>>>>>>> Of course, this isn't OO and makes it harder to detect argument >>>>>>>> name typos and such. >>>>>>>> >>>>>>>> The builder pattern could be implemented as so: >>>>>>>> >>>>>>>> // namespace for buttress_threaded_nut_builder >>>>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>>>> function new() = >>>>>>>>  let ( profile = [ >>>>>>>>      [  -1/2, -0.77], >>>>>>>>      [ -7/16, -0.75], >>>>>>>>      [  5/16,  0], >>>>>>>>      [  7/16,  0], >>>>>>>>      [  7/16, -0.75], >>>>>>>>      [  1/ 2, -0.77], >>>>>>>>  ]) >>>>>>>>  struct(obj)->profile(profile); >>>>>>>> >>>>>>>> so not any more or less difficult to write. What do profile and >>>>>>>> positioning look like? >>>>>>>> >>>>>>>> // namespace of generic_threaded_rod_builder >>>>>>>> function profile(o is builder_obj, profile) = >>>>>>>>  assert(is_list(profile)) // And other tests independent of >>>>>>>> other values >>>>>>>>  struct(o,profile=profile); >>>>>>>> >>>>>>>> // namespace of attachable_builder >>>>>>>> function positioning(o is attachable_builder_obj, anchor, spin, >>>>>>>> orient) = >>>>>>>>  assert(/* tests related to the parameters*/) >>>>>>>>  let( >>>>>>>>      l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>>         is_undef(spin) ? [] : [["spin", spin]], >>>>>>>>         is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>>      ) >>>>>>>>  struct(o,l); >>>>>>>> >>>>>>>> Okay, not in love with the repetition in there. But there are >>>>>>>> some improvements here: >>>>>>>> >>>>>>>> 1. Some validation can now stop cluttering the top of so many >>>>>>>> functions/modules. >>>>>>>> 2. The `get_radius` function doesn't need its args filled out >>>>>>>> every time >>>>>>>> 3. We're reusing attachable instead of needing to redundantly >>>>>>>> pass it so many args every single time and in every function >>>>>>>> and module signature. >>>>>>>> 4. We can match on the old types and convert to objects as needed >>>>>>>> >>>>>>>> Unsolved issue: why would -> method invocation not look at the >>>>>>>> namespace of positioning? I didn't intend it to, but it would >>>>>>>> do the wrong thing as written; only the calls in the new() >>>>>>>> methods should add the namespace to the method lookup. This >>>>>>>> might be best as `object` vs `struct` keywords. >>>>>>>> Maybe attachable should be a mixin instead of in the class >>>>>>>> hierarchy. >>>>>>>> >>>>>>>> So how would I write this with `this` as currently proposed? >>>>>>>> >>>>>>>> function buttress_threaded_nut_builder = >>>>>>>>  let ( profile = [ >>>>>>>>      [  -1/2, -0.77], >>>>>>>>      [ -7/16, -0.75], >>>>>>>>      [  5/16,  0], >>>>>>>>      [  7/16,  0], >>>>>>>>      [  7/16, -0.75], >>>>>>>>      [  1/ 2, -0.77], >>>>>>>>  ]) >>>>>>>>  object(new_generic_threaded_rod_builder().set_profile(profile), >>>>>>>> /* all methods on buttress_threaded_nut_builder must be defined >>>>>>>> here */); >>>>>>>> >>>>>>>> // in method list of generic_threaded_rod_builder >>>>>>>> function set_profile(profile) = >>>>>>>>  assert(is_list(profile)) // And other tests independent of >>>>>>>> other values >>>>>>>>  object(this,profile=profile); >>>>>>>> >>>>>>>> // in method list of attachable_builder >>>>>>>> function set_positioning(anchor, spin, orient) = >>>>>>>>  assert(/* tests related to the parameters*/) >>>>>>>>  let( >>>>>>>>      l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>>         is_undef(spin) ? [] : [["spin", spin]], >>>>>>>>         is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>>      ) >>>>>>>>  struct(this,l); >>>>>>>> >>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>>  reify_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> >>>>>>>> module reify_threaded_nut(obj) { >>>>>>>>  reify_generic_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>>>> >>>>>>>> >>>>>>>> ... >>>>>>>> >>>>>>>> Less different than I thought. Cascading explicit reify modules >>>>>>>> are annoying. For this usage the inability to write your own >>>>>>>> generic methods dispatching on other object types does not >>>>>>>> hinder anything. The shared namespace means methods and values >>>>>>>> must have unique names. I think this is supposed to solve name >>>>>>>> conflicts by having you only need one unique name per file and >>>>>>>> you put all your constants in there? And methods, I guess, so >>>>>>>> at the top of f.ex. bosl2/threading.scad there would be: >>>>>>>> >>>>>>>> bosl2_threading = object( >>>>>>>>  top_level_constant = 27, >>>>>>>>  function buttress_threaded_nut_builder() = >>>>>>>> ... >>>>>>>> ); >>>>>>>> >>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>>  bosl2_threading.top_level_constant; // for whatever reason >>>>>>>>  reify_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>>>> _______________________________________________ >>>>>>>> OpenSCAD mailing list >>>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>>> >>>>>>> _______________________________________________ >>>>>>> OpenSCAD mailing list >>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>> _______________________________________________ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> <mailto:discuss-leave@lists.openscad.org> >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> <mailto:discuss-leave@lists.openscad.org> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org > > _______________________________________________ > 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
CC
Cory Cross
Sat, Aug 23, 2025 3:23 PM

On 8/23/25 6:47 AM, Peter Kriens via Discuss wrote:

Then make a proposal.

We're discussing my proposal now.

However, for me it is absolutely crucial to have a mechanism for
methods: functions that can access their owning map, key-value
pairs, associative array, dictionary, symbol table, struct, record,
lookup table, registry, index, function, and dare I say it,
object,foobars, etc. /because it helps scoping the names/.

The language Elixir (and Erlang), a language with immutable types, has
namespacing without methods
https://hexdocs.pm/elixir/modules-and-functions.html:

|defmoduleMathdodefsum(a,b)doa+bendendIO.putsMath.sum(1,2)|

To summarize the above, this defines a namespace called Math. The
function sum is defined in the namespace Math. The last line invokes the
function puts from the namespace IO with a single argument, which is the
result from calling the function sum in the namespace Math with two
numerical arguments, 1 and 2.

So you can't write "something".capitalize() but you write
String.Capitalize "something". This solves your requirement of /because
it helps scoping the names/.

We could have:

namespace bosl2_threading {
    module generic_nut(<existing args>) { <existing body> }
    module nut(<existing args>) { <existing body>
generic_nut(<existing>) } // Do not have to specify namespace for
functions/modules in same namespace.
}

And instead of existing code of

threaded_nut(<existing args>);

we have

bosl2_threading.nut(<existing args>);

and/or

using bosl2_threading;
nut(<existing args>);
nut(<existing args>);
nut(<existing args>);

Since you have derailed that as well, any proposal for this?

I'm amused at my power, but please let me know what you think about the
above. This means leaving "object" as it's currently implemented, a map.

  • Cory

Sent from my iPad

On 22 Aug 2025, at 18:06, Cory Cross via Discuss
discuss@lists.openscad.org wrote:



On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss
discuss@lists.openscad.org wrote:

This discussion is imho a perfect example of bikeshedding
https://en.wiktionary.org/wiki/bikeshedding. We can discuss this
forever on a forum like this because we're all highly experienced &
opinionated but in the mean time OpenSCAD has no KV pairs

Let's stop trying to make them objects and just call them dictionary,
map, associative array, or any of the many other names for this:
https://en.m.wikipedia.org/wiki/Associative_array

The words you use carry connotations and have denotations that users
will use to understand what it is. If you offer to pay 100 Euros and
show up with a 1-Euro bill, the other party is going to be upset and
confused even if you explain you don't pronounce the "ents" of "cents".

  • Cory

I personally would appreciate a time box so we can make sure this
does not get delayed another two years again.

   Peter

On 21 Aug 2025, at 23:18, Cory Cross via Discuss
discuss@lists.openscad.org wrote:

On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss
<discuss@lists.openscad.org mailto:discuss@lists.openscad.org> wrote:

On 21 Aug 2025, at 01:55, Cory Cross via Discuss
<discuss@lists.openscad.org mailto:discuss@lists.openscad.org>
wrote:

Objects do NOT have parents so I am not sure what you're talking
about? An object is a flat set of key-value pairs. There is no
hierarchy.

Then they shouldn't be called objects. At least 99% of people who
have or will use OpenSCAD will associate objects with the
mainstream object-oriented languages which all put inheritance
front-and-center. It's day-one "learning Python" material. I don't
think there's a single language with a "this" keyword that doesn't
have inheritance. You're setting up people for confusion.

We are very intentionally not implementing a full blow OO system
to keep OpenSCAD as simple as possible, but not simpler.

Following this logic, not adding it is simpler.

What do you want to be simple: writing SCAD or the implementation
of OpenSCAD? There's often (but not always) a trade-off. Brainfuck
is very simple to implement. You can solve some very complicated
analyses in a single line of Mathematica.

So we already have the power to do builders, it's just slightly
uglier and slower. My question is while settle for "this" when
generic functions are even nicer?

Slightly??? We must live in another universe. There are few types
of code I'd like to write less than this kind of boiler plate
code. I wrote several of these 'OO' systems but they were quite ugly.

"this" refers to the proposal to add a "this" keyword. But what I
wrote shows that, with a couple helper functions, the proposed
object() does not result in substantially simpler code and by the
maxim "keep OpenSCAD as simple as possible, but not simpler",
shouldn't be added.

Then you threw in an a-bom ... and another one. I am getting a bit
desperate and feel this is going way off the track and taking way
too much of my time ...

I am new here so I might not understand the mores in this project.

I am new as well (though a user for many years).

However, in other projects I am used that if you want to derail a
PR you make a fully working counter PR so people can play with the
proposals and compare. I find that you're now just dropping
disruptive ideas ...

I've not found any other discussion of OEP8 and wasn't active at
the time anyway. I am discussing now because now is when I'm here.

It's my impression the "this" keyword is just being added because
people are unfamiliar with other systems. If SCAD was a hybrid
procedural/OO lisp with mutable values like JavaScript, then it'd
be fine to copy their semantics. But it's not and I think you're
going down the wrong road.

I'm trying to prove it by picking bosl2 and showing how I'd
refactor it using the proposed "this" approach or the one I'm
proposing, because ultimately what we want is what makes it easier
and faster to write correct code, right?

Everything I've proposed is quite easy to implement and I'll be
happy to do it and/or collaborate on it.

The danger here is that we spend a lot of time talking back and
forth and then nothing happens again because everything got so
complicated. I think this partly happened with OEP8 and that spent
a lot of time in discussion. There are very good, some crucial,
ideas in that PR that has been idling since 2023.

I also think it's important to keep momentum up.

I know I can be a bit blunt but you can blame it on my Dutch
citizenship ;-)

I can be a bit blunt but you can blame it on my Dutch ancestry :-).

  • Cory Cross

   Peter

Actually, plists are better because you can get super methods :-)

I'm on my phone composing this without Internet access, so please
forgive the formatting and mild syntax errors.

On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss
discuss@lists.openscad.org wrote:

You flabbergasted me with the completely different
direction/syntax you took but then at the end I saw that you
came to the conclusion you could do all this also with 'this'
and object? Not good for my blood pressure! :-)

I like the builder approach and it is one of my drivers for the
object work and $this.

BTW, notice that OEP8 also proposed to have modules as
expression. I am currently working on a PR for this. This will
allow builders to also call modules, have modules as variables,
and hopefully modules as methods when we can finally close [the
$]this discussion ...

   Peter Kriens

On 19 Aug 2025, at 07:47, Cory Cross via Discuss
discuss@lists.openscad.org wrote:
On 8/16/25 8:49 AM, Jordan Brown wrote:

bosl2::threading::nut_builder::new(required, args,
here)->optional_generic_arg(its_value)->reify();

I'm very sympathetic to the desire to reduce repetition in
argument handling, but I'm not understanding what that means
at all.  Partly that's presentation; is this intended to be
how the library would say something, or how the caller would
invoke the function?
If the former, I don't understand what it means.  If the
latter, are you seriously suggesting this as a replacement for
threaded_nut(required, args, here,
optional_generic_arc=its_value);

I am suggesting it as a replacement for the latter; not because
it's better for the user, but because it's better for the
maintainers and not worse for the users. (I would assume we'd
add using bosl2::threading to shorten names, at some point).

As a practical example, here is a invocation of a threaded
module in my code:

buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK);

here's how I would do it with the builder pattern and the
suggested OO approach:

buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify;

You don't have to use the builder pattern. You could choose to
use objects as a replacement for Python's **kwargs:

buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK));

In this case, buttress_threaded_nut's implementation would
change from 50 lines to 11:

module buttress_threaded_nut(kwargs) {
 profile = [
     [  -1/2, -0.77],
     [ -7/16, -0.75],
     [  5/16,  0],
     [  7/16,  0],
     [  7/16, -0.75],
     [  1/ 2, -0.77],
 ];
 generic_threaded_nut(struct(kwargs, profile=profile));
}

Of course, this isn't OO and makes it harder to detect argument
name typos and such.

The builder pattern could be implemented as so:

// namespace for buttress_threaded_nut_builder
obj = struct(generic_threaded_nut_builder::new());
function new() =
 let ( profile = [
     [  -1/2, -0.77],
     [ -7/16, -0.75],
     [  5/16,  0],
     [  7/16,  0],
     [  7/16, -0.75],
     [  1/ 2, -0.77],
 ])
 struct(obj)->profile(profile);

so not any more or less difficult to write. What do profile and
positioning look like?

// namespace of generic_threaded_rod_builder
function profile(o is builder_obj, profile) =
 assert(is_list(profile)) // And other tests independent of
other values
 struct(o,profile=profile);

// namespace of attachable_builder
function positioning(o is attachable_builder_obj, anchor, spin,
orient) =
 assert(/* tests related to the parameters*/)
 let(
     l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
        is_undef(spin) ? [] : [["spin", spin]],
        is_undef(orient) ? [] : [["orient", orient]])
     )
 struct(o,l);

Okay, not in love with the repetition in there. But there are
some improvements here:

  1. Some validation can now stop cluttering the top of so many
    functions/modules.
  2. The get_radius function doesn't need its args filled out
    every time
  3. We're reusing attachable instead of needing to redundantly
    pass it so many args every single time and in every function
    and module signature.
  4. We can match on the old types and convert to objects as needed

Unsolved issue: why would -> method invocation not look at the
namespace of positioning? I didn't intend it to, but it would
do the wrong thing as written; only the calls in the new()
methods should add the namespace to the method lookup. This
might be best as object vs struct keywords.
Maybe attachable should be a mixin instead of in the class
hierarchy.

So how would I write this with this as currently proposed?

function buttress_threaded_nut_builder =
 let ( profile = [
     [  -1/2, -0.77],
     [ -7/16, -0.75],
     [  5/16,  0],
     [  7/16,  0],
     [  7/16, -0.75],
     [  1/ 2, -0.77],
 ])
 object(new_generic_threaded_rod_builder().set_profile(profile),
/* all methods on buttress_threaded_nut_builder must be defined
here */);

// in method list of generic_threaded_rod_builder
function set_profile(profile) =
 assert(is_list(profile)) // And other tests independent of
other values
 object(this,profile=profile);

// in method list of attachable_builder
function set_positioning(anchor, spin, orient) =
 assert(/* tests related to the parameters*/)
 let(
     l = concat(is_undef(anchor) ? [] : [["anchor", anchor]],
        is_undef(spin) ? [] : [["spin", spin]],
        is_undef(orient) ? [] : [["orient", orient]])
     )
 struct(this,l);

module reify_buttress_threaded_nut(obj) {
 reify_threaded_nut(obj);
}

module reify_threaded_nut(obj) {
 reify_generic_threaded_nut(obj);
}

reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK));

...

Less different than I thought. Cascading explicit reify modules
are annoying. For this usage the inability to write your own
generic methods dispatching on other object types does not
hinder anything. The shared namespace means methods and values
must have unique names. I think this is supposed to solve name
conflicts by having you only need one unique name per file and
you put all your constants in there? And methods, I guess, so
at the top of f.ex. bosl2/threading.scad there would be:

bosl2_threading = object(
 top_level_constant = 27,
 function buttress_threaded_nut_builder() =
...
);

module reify_buttress_threaded_nut(obj) {
 bosl2_threading.top_level_constant; // for whatever reason
 reify_threaded_nut(obj);
}

I've seen worse. I certainly can't say this rules out this.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
mailto:discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org

On 8/23/25 6:47 AM, Peter Kriens via Discuss wrote: > Then make a proposal. We're discussing my proposal now. > However, for me it is absolutely crucial to have a mechanism for > _methods_: functions that can access their owning map, key-value > pairs, associative array, dictionary, symbol table, struct, record, > lookup table, registry, index, function, and dare I say it, > object,foobars, etc. /because it helps scoping the names/. The language Elixir (and Erlang), a language with immutable types, has namespacing without methods <https://hexdocs.pm/elixir/modules-and-functions.html>: |defmoduleMathdodefsum(a,b)doa+bendendIO.putsMath.sum(1,2)| To summarize the above, this defines a namespace called Math. The function sum is defined in the namespace Math. The last line invokes the function puts from the namespace IO with a single argument, which is the result from calling the function sum in the namespace Math with two numerical arguments, 1 and 2. So you can't write "something".capitalize() but you write String.Capitalize "something". This solves your requirement of /because it helps scoping the names/. We could have: namespace bosl2_threading {     module generic_nut(<existing args>) { <existing body> }     module nut(<existing args>) { <existing body> generic_nut(<existing>) } // Do not have to specify namespace for functions/modules in same namespace. } And instead of existing code of threaded_nut(<existing args>); we have bosl2_threading.nut(<existing args>); and/or using bosl2_threading; nut(<existing args>); nut(<existing args>); nut(<existing args>); > Since you have derailed that as well, any proposal for this? I'm amused at my power, but please let me know what you think about the above. This means leaving "object" as it's currently implemented, a map. - Cory > > > Sent from my iPad > >> On 22 Aug 2025, at 18:06, Cory Cross via Discuss >> <discuss@lists.openscad.org> wrote: >> >>  >> >> On August 22, 2025 10:14:28 AM EDT, Peter Kriens via Discuss >> <discuss@lists.openscad.org> wrote: >>> This discussion is imho a perfect example of bikeshedding >>> <https://en.wiktionary.org/wiki/bikeshedding>. We can discuss this >>> forever on a forum like this because we're all highly experienced & >>> opinionated but in the mean time OpenSCAD has no KV pairs >> >> Let's stop trying to make them objects and just call them dictionary, >> map, associative array, or any of the many other names for this: >> https://en.m.wikipedia.org/wiki/Associative_array >> >> The words you use carry connotations and have denotations that users >> will use to understand what it is. If you offer to pay 100 Euros and >> show up with a 1-Euro bill, the other party is going to be upset and >> confused even if you explain you don't pronounce the "ents" of "cents". >> >> - Cory >> >> >>> I personally would appreciate a time box so we can make sure this >>> does not get delayed another two years again. >> >> >> >>> >>>    Peter >>> >>> >>>> On 21 Aug 2025, at 23:18, Cory Cross via Discuss >>>> <discuss@lists.openscad.org> wrote: >>>> >>>> >>>> >>>> On August 21, 2025 9:57:30 AM EDT, Peter Kriens via Discuss >>>> <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> wrote: >>>>>> On 21 Aug 2025, at 01:55, Cory Cross via Discuss >>>>>> <discuss@lists.openscad.org <mailto:discuss@lists.openscad.org>> >>>>>> wrote: >>>>> Objects do NOT have parents so I am not sure what you're talking >>>>> about? An object is a flat set of key-value pairs. There is no >>>>> hierarchy. >>>> >>>> Then they shouldn't be called objects. At least 99% of people who >>>> have or will use OpenSCAD will associate objects with the >>>> mainstream object-oriented languages which all put inheritance >>>> front-and-center. It's day-one "learning Python" material. I don't >>>> think there's a single language with a "this" keyword that doesn't >>>> have inheritance. You're setting up people for confusion. >>>> >>>>> We are very intentionally not implementing a full blow OO system >>>>> to keep OpenSCAD as simple as possible, but not simpler. >>>> >>>> Following this logic, not adding it is simpler. >>>> >>>> What do you want to be simple: writing SCAD or the implementation >>>> of OpenSCAD? There's often (but not always) a trade-off. Brainfuck >>>> is very simple to implement. You can solve some very complicated >>>> analyses in a single line of Mathematica. >>>> >>>>>> So we already have the power to do builders, it's just slightly >>>>>> uglier and slower. My question is while settle for "this" when >>>>>> generic functions are even nicer? >>>>> >>>>> Slightly??? We must live in another universe. There are few types >>>>> of code I'd like to write less than this kind of boiler plate >>>>> code. I wrote several of these 'OO' systems but they were quite ugly. >>>> >>>> "this" refers to the proposal to add a "this" keyword. But what I >>>> wrote shows that, with a couple helper functions, the proposed >>>> object() does not result in substantially simpler code and by the >>>> maxim "keep OpenSCAD as simple as possible, but not simpler", >>>> shouldn't be added. >>>> >>>>> Then you threw in an a-bom ... and another one. I am getting a bit >>>>> desperate and feel this is going way off the track and taking way >>>>> too much of my time ... >>>>> >>>>> I am new here so I might not understand the mores in this project. >>>> >>>> I am new as well (though a user for many years). >>>> >>>>> However, in other projects I am used that if you want to derail a >>>>> PR you make a fully working counter PR so people can play with the >>>>> proposals and compare. I find that you're now just dropping >>>>> disruptive ideas ... >>>> >>>> I've not found any other discussion of OEP8 and wasn't active at >>>> the time anyway. I am discussing now because now is when I'm here. >>>> >>>> It's my impression the "this" keyword is just being added because >>>> people are unfamiliar with other systems. If SCAD was a hybrid >>>> procedural/OO lisp with mutable values like JavaScript, then it'd >>>> be fine to copy their semantics. But it's not and I think you're >>>> going down the wrong road. >>>> >>>> I'm trying to prove it by picking bosl2 and showing how I'd >>>> refactor it using the proposed "this" approach or the one I'm >>>> proposing, because ultimately what we want is what makes it easier >>>> and faster to write correct code, right? >>>> >>>> Everything I've proposed is quite easy to implement and I'll be >>>> happy to do it and/or collaborate on it. >>>> >>>>> The danger here is that we spend a lot of time talking back and >>>>> forth and then nothing happens again because everything got so >>>>> complicated. I think this partly happened with OEP8 and that spent >>>>> a lot of time in discussion. There are very good, some crucial, >>>>> ideas in that PR that has been idling since 2023. >>>> >>>> I also think it's important to keep momentum up. >>>> >>>>> I know I can be a bit blunt but you can blame it on my Dutch >>>>> citizenship ;-) >>>> >>>> I can be a bit blunt but you can blame it on my Dutch ancestry :-). >>>> >>>> - Cory Cross >>>> >>>>> >>>>>    Peter >>>>> >>>>> >>>>> >>>>>> >>>>>> Actually, plists are better because you can get super methods :-) >>>>>> >>>>>> >>>>>> I'm on my phone composing this without Internet access, so please >>>>>> forgive the formatting and mild syntax errors. >>>>>> >>>>>> >>>>>> >>>>>> On August 19, 2025 4:40:20 AM EDT, Peter Kriens via Discuss >>>>>> <discuss@lists.openscad.org> wrote: >>>>>>> You flabbergasted me with the completely different >>>>>>> direction/syntax you took but then at the end I saw that you >>>>>>> came to the conclusion you could do all this also with 'this' >>>>>>> and `object`? Not good for my blood pressure! :-) >>>>>>> >>>>>>> I like the builder approach and it is one of my drivers for the >>>>>>> object work and $this. >>>>>>> >>>>>>> BTW, notice that OEP8 also proposed to have modules as >>>>>>> expression. I am currently working on a PR for this. This will >>>>>>> allow builders to also call modules, have modules as variables, >>>>>>> and hopefully modules as methods when we can finally close [the >>>>>>> $]this discussion ... >>>>>>> >>>>>>>    Peter Kriens >>>>>>> >>>>>>>> On 19 Aug 2025, at 07:47, Cory Cross via Discuss >>>>>>>> <discuss@lists.openscad.org> wrote: >>>>>>>> On 8/16/25 8:49 AM, Jordan Brown wrote: >>>>>>>>>> bosl2::threading::nut_builder::new(required, args, >>>>>>>>>> here)->optional_generic_arg(its_value)->reify(); >>>>>>>>> I'm very sympathetic to the desire to reduce repetition in >>>>>>>>> argument handling, but I'm not understanding what that means >>>>>>>>> at all.  Partly that's presentation; is this intended to be >>>>>>>>> how the library would say something, or how the caller would >>>>>>>>> invoke the function? >>>>>>>>> If the former, I don't understand what it means.  If the >>>>>>>>> latter, are you seriously suggesting this as a replacement for >>>>>>>>> threaded_nut(required, args, here, >>>>>>>>> optional_generic_arc=its_value); >>>>>>>> I am suggesting it as a replacement for the latter; not because >>>>>>>> it's better for the user, but because it's better for the >>>>>>>> maintainers and not worse for the users. (I would assume we'd >>>>>>>> add `using bosl2::threading` to shorten names, at some point). >>>>>>>> >>>>>>>> As a practical example, here is a invocation of a threaded >>>>>>>> module in my code: >>>>>>>> >>>>>>>> buttress_threaded_nut(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK); >>>>>>>> >>>>>>>> here's how I would do it with the builder pattern and the >>>>>>>> suggested OO approach: >>>>>>>> >>>>>>>> buttress_threaded_nut_builder::new()->nutwidth(10)->id(7+.17*4)->h(6)->shape("square")->positioning(orient=DOWN,anchor=TOP+BACK)->reify; >>>>>>>> >>>>>>>> You don't have to use the builder pattern. You could choose to >>>>>>>> use objects as a replacement for Python's **kwargs: >>>>>>>> >>>>>>>> buttress_threaded_nut(struct(nutwidth=10,id=7+.17*4,h=6,pitch=1,shape="square",orient=DOWN,anchor=TOP+BACK)); >>>>>>>> >>>>>>>> In this case, buttress_threaded_nut's implementation would >>>>>>>> change from 50 lines to 11: >>>>>>>> >>>>>>>> module buttress_threaded_nut(kwargs) { >>>>>>>>  profile = [ >>>>>>>>      [  -1/2, -0.77], >>>>>>>>      [ -7/16, -0.75], >>>>>>>>      [  5/16,  0], >>>>>>>>      [  7/16,  0], >>>>>>>>      [  7/16, -0.75], >>>>>>>>      [  1/ 2, -0.77], >>>>>>>>  ]; >>>>>>>>  generic_threaded_nut(struct(kwargs, profile=profile)); >>>>>>>> } >>>>>>>> >>>>>>>> Of course, this isn't OO and makes it harder to detect argument >>>>>>>> name typos and such. >>>>>>>> >>>>>>>> The builder pattern could be implemented as so: >>>>>>>> >>>>>>>> // namespace for buttress_threaded_nut_builder >>>>>>>> obj = struct(generic_threaded_nut_builder::new()); >>>>>>>> function new() = >>>>>>>>  let ( profile = [ >>>>>>>>      [  -1/2, -0.77], >>>>>>>>      [ -7/16, -0.75], >>>>>>>>      [  5/16,  0], >>>>>>>>      [  7/16,  0], >>>>>>>>      [  7/16, -0.75], >>>>>>>>      [  1/ 2, -0.77], >>>>>>>>  ]) >>>>>>>>  struct(obj)->profile(profile); >>>>>>>> >>>>>>>> so not any more or less difficult to write. What do profile and >>>>>>>> positioning look like? >>>>>>>> >>>>>>>> // namespace of generic_threaded_rod_builder >>>>>>>> function profile(o is builder_obj, profile) = >>>>>>>>  assert(is_list(profile)) // And other tests independent of >>>>>>>> other values >>>>>>>>  struct(o,profile=profile); >>>>>>>> >>>>>>>> // namespace of attachable_builder >>>>>>>> function positioning(o is attachable_builder_obj, anchor, spin, >>>>>>>> orient) = >>>>>>>>  assert(/* tests related to the parameters*/) >>>>>>>>  let( >>>>>>>>      l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>>         is_undef(spin) ? [] : [["spin", spin]], >>>>>>>>         is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>>      ) >>>>>>>>  struct(o,l); >>>>>>>> >>>>>>>> Okay, not in love with the repetition in there. But there are >>>>>>>> some improvements here: >>>>>>>> >>>>>>>> 1. Some validation can now stop cluttering the top of so many >>>>>>>> functions/modules. >>>>>>>> 2. The `get_radius` function doesn't need its args filled out >>>>>>>> every time >>>>>>>> 3. We're reusing attachable instead of needing to redundantly >>>>>>>> pass it so many args every single time and in every function >>>>>>>> and module signature. >>>>>>>> 4. We can match on the old types and convert to objects as needed >>>>>>>> >>>>>>>> Unsolved issue: why would -> method invocation not look at the >>>>>>>> namespace of positioning? I didn't intend it to, but it would >>>>>>>> do the wrong thing as written; only the calls in the new() >>>>>>>> methods should add the namespace to the method lookup. This >>>>>>>> might be best as `object` vs `struct` keywords. >>>>>>>> Maybe attachable should be a mixin instead of in the class >>>>>>>> hierarchy. >>>>>>>> >>>>>>>> So how would I write this with `this` as currently proposed? >>>>>>>> >>>>>>>> function buttress_threaded_nut_builder = >>>>>>>>  let ( profile = [ >>>>>>>>      [  -1/2, -0.77], >>>>>>>>      [ -7/16, -0.75], >>>>>>>>      [  5/16,  0], >>>>>>>>      [  7/16,  0], >>>>>>>>      [  7/16, -0.75], >>>>>>>>      [  1/ 2, -0.77], >>>>>>>>  ]) >>>>>>>>  object(new_generic_threaded_rod_builder().set_profile(profile), >>>>>>>> /* all methods on buttress_threaded_nut_builder must be defined >>>>>>>> here */); >>>>>>>> >>>>>>>> // in method list of generic_threaded_rod_builder >>>>>>>> function set_profile(profile) = >>>>>>>>  assert(is_list(profile)) // And other tests independent of >>>>>>>> other values >>>>>>>>  object(this,profile=profile); >>>>>>>> >>>>>>>> // in method list of attachable_builder >>>>>>>> function set_positioning(anchor, spin, orient) = >>>>>>>>  assert(/* tests related to the parameters*/) >>>>>>>>  let( >>>>>>>>      l = concat(is_undef(anchor) ? [] : [["anchor", anchor]], >>>>>>>>         is_undef(spin) ? [] : [["spin", spin]], >>>>>>>>         is_undef(orient) ? [] : [["orient", orient]]) >>>>>>>>      ) >>>>>>>>  struct(this,l); >>>>>>>> >>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>>  reify_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> >>>>>>>> module reify_threaded_nut(obj) { >>>>>>>>  reify_generic_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> reify_buttress_threaded_nut(buttress_threaded_nut_builder().set_nutwidth(10)->set_id(7+.17*4)->set_h(6)->set_shape("square")->set_positioning(orient=DOWN,anchor=TOP+BACK)); >>>>>>>> >>>>>>>> >>>>>>>> ... >>>>>>>> >>>>>>>> Less different than I thought. Cascading explicit reify modules >>>>>>>> are annoying. For this usage the inability to write your own >>>>>>>> generic methods dispatching on other object types does not >>>>>>>> hinder anything. The shared namespace means methods and values >>>>>>>> must have unique names. I think this is supposed to solve name >>>>>>>> conflicts by having you only need one unique name per file and >>>>>>>> you put all your constants in there? And methods, I guess, so >>>>>>>> at the top of f.ex. bosl2/threading.scad there would be: >>>>>>>> >>>>>>>> bosl2_threading = object( >>>>>>>>  top_level_constant = 27, >>>>>>>>  function buttress_threaded_nut_builder() = >>>>>>>> ... >>>>>>>> ); >>>>>>>> >>>>>>>> module reify_buttress_threaded_nut(obj) { >>>>>>>>  bosl2_threading.top_level_constant; // for whatever reason >>>>>>>>  reify_threaded_nut(obj); >>>>>>>> } >>>>>>>> >>>>>>>> I've seen worse. I certainly can't say this rules out `this`. >>>>>>>> _______________________________________________ >>>>>>>> OpenSCAD mailing list >>>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>>> >>>>>>> _______________________________________________ >>>>>>> OpenSCAD mailing list >>>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>>> _______________________________________________ >>>>>> OpenSCAD mailing list >>>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> _______________________________________________ >>>>> OpenSCAD mailing list >>>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>>> <mailto:discuss-leave@lists.openscad.org> >>>> _______________________________________________ >>>> OpenSCAD mailing list >>>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>>> <mailto:discuss-leave@lists.openscad.org> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email todiscuss-leave@lists.openscad.org
CC
Cory Cross
Sat, Aug 23, 2025 6:15 PM

On 8/22/25 10:45 PM, Revar Desmera via Discuss wrote:

Really, object() isn't something that a regular user is going to use
often, much less the $this variable, or module literals. Simple
projects do not need object orientation.  This is a set of features
targeted for library writers, or large code bases, or for complicated
algorithms.  The vast majority of users will only need to know the dot
(.) syntax to use it, preserving the apparent simplicity of the language.

How do you feel about needing to explicitly put anchor, spin, orient in
nearly all function and method signatures? Additionally the threaded
parameters, all the top/bottom inner/outer radius/diameter parameters, etc.

I've still not finished up a new joiner because it seems annoying to add
those things (it works for me, but I want to add these things before
submitting).

I think it'd be really nice if you could mark a module or function as an
attachable and get the final stuff handled for you as much as possible.
I also found it cumbersome and/or easily-missed to add the radius and
diameter options, as did the person writing the PR about the D-ring. I
would find it beneficial to be able to mark my module as having top and
bottom inner and outer circles.

  • Cory Cross
 bosl = use("BOSL3/std.scad");

 bosl.Cuboid([30,40,50])
     .Chamfer(5, angle=60)
     .Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT)
     .Show();

Note that builder design pattern makes the code much more verbose, so
that seems like an awful example.

  • Revar

OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org

On 8/22/25 10:45 PM, Revar Desmera via Discuss wrote: > Really, `object()` isn't something that a regular user is going to use > often, much less the `$this` variable, or module literals. Simple > projects do not need object orientation.  This is a set of features > targeted for library writers, or large code bases, or for complicated > algorithms.  The vast majority of users will only need to know the dot > (.) syntax to use it, preserving the apparent simplicity of the language. How do you feel about needing to explicitly put anchor, spin, orient in nearly all function and method signatures? Additionally the threaded parameters, all the top/bottom inner/outer radius/diameter parameters, etc. I've still not finished up a new joiner because it seems annoying to add those things (it works for me, but I want to add these things before submitting). I think it'd be really nice if you could mark a module or function as an attachable and get the final stuff handled for you as much as possible. I also found it cumbersome and/or easily-missed to add the radius and diameter options, as did the person writing the PR about the D-ring. I would find it beneficial to be able to mark my module as having top and bottom inner and outer circles. - Cory Cross > > bosl = use("BOSL3/std.scad"); > > bosl.Cuboid([30,40,50]) >     .Chamfer(5, angle=60) >     .Edges([bosl.TOP, bosl.LEFT],except=bosl.TOP+bosl.LEFT) >     .Show(); > > > Note that builder design pattern makes the code much more verbose, so > that seems like an awful example. > > - Revar > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email todiscuss-leave@lists.openscad.org
JB
Jordan Brown
Sat, Aug 23, 2025 8:37 PM

[ I thought that when we got off the boat into reliable cellular service
I would be more able to keep up.  Ha!  I have reliable network service
now, but much less time. ]

First, for the people who aren't interested in OO-like mechanisms and
are concerned about increasing complexity... the intent and expectation
is that this evolution will at worst be neutral for beginners, and at
best will improve things.  I don't see any reason to expect that today's
OpenSCAD programs will stop working, except perhaps in narrow corner
cases.  (For instance, maybe there will be a few new keywords that
can't be used as variable names.)  The stuff that we are discussing
about "this" and inheritance will have approximately zero impact on
beginning and intermediate OpenSCAD users.  Advanced OpenSCAD users
might want to take advantage of these mechanisms, but unless they want
to interact with libraries that use those mechanisms, they don't have to.

Cory says that inheritance is a "day one" item for learning Python.  I
don't happen to agree, but I think for OpenSCAD it is critical that
basically nothing associated with objects is a day-one item, or even a
month-one item.  Many users would never use any "object" mechanism, and
most of the rest would use only member access and method calls.  Only
very advanced users, primarily library authors, would create objects
with methods, and that's where all this discussion is.

Then they shouldn't be called objects.

As they stand today, they are indeed better mapped to Python
dictionaries.  We could call them dictionaries, records, or any of a
dozen other data-only names.  But then when and if we add more OO-like
features, starting with Peter's work on a "this" mechanism, we would be
stuck forever with a name that is inappropriate... or, worse, with two
similar-but-different concepts with different names, like Python has. 
My hope is to expand into supporting something more OO-ish, while trying
to avoid the complexity of most OO languages, expanding in bite-sized
chunks.

That last, expanding in bite-sized chunks, is important.  If we try to
do everything all at once, if we wait to introduce these features until
they do everything that we want, we are unlikely to ever get anything. 
There will always be something additional that somebody wants.  We need
a sequence of steps, each building on the last, and each allowing for
the next.

Where will we end up?  I don't know.  I doubt that it will be as
powerful as a "real" OO language... and that doesn't bother me in the
slightest.  Perhaps 90% of my interest is satisfied by the data-only
mechanism, especially once we add module references to parallel function
references.  (That's not directly related to OO, but plays well with
it.)  Probably 90% of the remainder is satisfied with "this".  If we
never get beyond that, if we never address that last 1%, I won't lose
any sleep.  If you really need more than what we end up with, you can
move over to PythonSCAD.

As for familiarity and similarity to other languages... What I've called
objects in OpenSCAD are patterned after JavaScript objects.  Their
access mechanisms are nearly identical to JS objects.[1]  With the OEP8
syntax (PR #6081), the creation syntax is a superset of JS's syntax.[2][3]

[1] JS's "for (k in obj)" becomes "for (k = obj)".
[2] OEP8 adds a mechanism for specifying dynamically-constructed
keys, and object comprehensions (a la OpenSCAD list comprehensions).
[3] I believe that with the OEP8 syntax OpenSCAD will be able to
directly consume JSON.

Touching on detail points:

Inheritance can be managed through cloning objects.  That yields a
mechanism very similar to JS's "prototype" mechanism.  object() already
allows cloning and object and modifying the result.

Like JS's mechanism, calling shadowed parent methods is ...
interesting.  (I should note that for work-legacy reasons almost all of
my JS experience is with 2006-era JS.)  I have two thoughts on the
subject.  First, when you clone the original object, you can copy a
method reference into your own name, preserving access to it:

o1 = object(method=function (arg) ... );
o2 = object(o1, superMethod=o1.method, method=function (arg) this.superMethod(arg) + 1);

That will work without any additional built-in mechanisms (beyond
"this", of course).

Alternatively, if there's a mechanism that allows calling a function and
handing it a "this" (JavaScript's Function.call), you can use that,
combined with access to the "parent" object, to call a parent method. 
Handwaving over the details of that mechanism, and using JS's mechanism
for the moment, you would do something like:

o2 = object(o1, method=function (arg) o1.method.call(this, arg) + 1);

Whether a library uses the builder pattern or some other style is up to
the library.  One of the other enhancements I'd like to see added is
varargs mechanisms akin to Python's *args and **kwargs mechanisms. 
Those mechanisms could be used to avoid duplicated argument parsing in a
library that wants to stick with the named-argument pattern.

I thought there was more that I wanted to write about, but I've lost track.

[ I thought that when we got off the boat into reliable cellular service I would be more able to keep up.  Ha!  I have reliable network service now, but much less time. ] First, for the people who *aren't* interested in OO-like mechanisms and are concerned about increasing complexity... the intent and expectation is that this evolution will at worst be neutral for beginners, and at best will improve things.  I don't see any reason to expect that today's OpenSCAD programs will stop working, except perhaps in narrow corner cases.  (For instance, *maybe* there will be a few new keywords that can't be used as variable names.)  The stuff that we are discussing about "this" and inheritance will have approximately zero impact on beginning and intermediate OpenSCAD users.  Advanced OpenSCAD users might want to take advantage of these mechanisms, but unless they want to interact with libraries that use those mechanisms, they don't have to. Cory says that inheritance is a "day one" item for learning Python.  I don't happen to agree, but I think for OpenSCAD it is *critical* that basically nothing associated with objects is a day-one item, or even a month-one item.  Many users would never use any "object" mechanism, and most of the rest would use only member access and method calls.  Only very advanced users, primarily library authors, would create objects with methods, and that's where all this discussion is. > Then they shouldn't be called objects. As they stand today, they are indeed better mapped to Python dictionaries.  We could call them dictionaries, records, or any of a dozen other data-only names.  But then when and if we add more OO-like features, starting with Peter's work on a "this" mechanism, we would be stuck forever with a name that is inappropriate... or, worse, with two similar-but-different concepts with different names, like Python has.  My hope is to expand into supporting something more OO-ish, while trying to avoid the complexity of most OO languages, expanding in bite-sized chunks. That last, expanding in bite-sized chunks, is important.  If we try to do everything all at once, if we wait to introduce these features until they do everything that we want, we are unlikely to ever get anything.  There will always be something additional that somebody wants.  We need a sequence of steps, each building on the last, and each allowing for the next. Where will we end up?  I don't know.  I doubt that it will be as powerful as a "real" OO language... and that doesn't bother me in the slightest.  Perhaps 90% of my interest is satisfied by the data-only mechanism, especially once we add module references to parallel function references.  (That's not directly related to OO, but plays well with it.)  Probably 90% of the remainder is satisfied with "this".  If we never get beyond that, if we never address that last 1%, I won't lose any sleep.  If you really need more than what we end up with, you can move over to PythonSCAD. As for familiarity and similarity to other languages... What I've called objects in OpenSCAD are patterned after JavaScript objects.  Their access mechanisms are nearly identical to JS objects.[1]  With the OEP8 syntax (PR #6081), the creation syntax is a superset of JS's syntax.[2][3] [1] JS's "for (k in obj)" becomes "for (k = obj)". [2] OEP8 adds a mechanism for specifying dynamically-constructed keys, and object comprehensions (a la OpenSCAD list comprehensions). [3] I believe that with the OEP8 syntax OpenSCAD will be able to directly consume JSON. --- Touching on detail points: Inheritance can be managed through cloning objects.  That yields a mechanism very similar to JS's "prototype" mechanism.  object() already allows cloning and object and modifying the result. Like JS's mechanism, calling shadowed parent methods is ... interesting.  (I should note that for work-legacy reasons almost all of my JS experience is with 2006-era JS.)  I have two thoughts on the subject.  First, when you clone the original object, you can copy a method reference into your own name, preserving access to it: o1 = object(method=function (arg) ... ); o2 = object(o1, superMethod=o1.method, method=function (arg) this.superMethod(arg) + 1); That will work without any additional built-in mechanisms (beyond "this", of course). Alternatively, if there's a mechanism that allows calling a function and handing it a "this" (JavaScript's Function.call), you can use that, combined with access to the "parent" object, to call a parent method.  Handwaving over the details of that mechanism, and using JS's mechanism for the moment, you would do something like: o2 = object(o1, method=function (arg) o1.method.call(this, arg) + 1); Whether a library uses the builder pattern or some other style is up to the library.  One of the other enhancements I'd like to see added is varargs mechanisms akin to Python's *args and **kwargs mechanisms.  Those mechanisms could be used to avoid duplicated argument parsing in a library that wants to stick with the named-argument pattern. I thought there was more that I wanted to write about, but I've lost track.
CC
Cory Cross
Sat, Aug 23, 2025 9:42 PM

On 8/23/25 1:37 PM, Jordan Brown via Discuss wrote:

Cory says that inheritance is a "day one" item for learning Python.  I
don't happen to agree, but I think for OpenSCAD it is critical that
basically nothing associated with objects is a day-one item, or even a
month-one item.

My point was that anyone who knows anything about objects and
programming is going to expect inheritance if you say methods and
object, not that someone who learns OpenSCAD needs to know inheritance
to model.

You call it a dictionary and say it has scoped functions, they're going
to think "huh".

As they stand today, they are indeed better mapped to Python
dictionaries.  We could call them dictionaries, records, or any of a
dozen other data-only names.  But then when and if we add more OO-like
features, starting with Peter's work on a "this" mechanism, we would
be stuck forever with a name that is inappropriate...

My suggestion is that we should not add any OO-like features to the
key-value store unless we've thought through the design.

or, worse, with two similar-but-different concepts with different
names, like Python has.  My hope is to expand into supporting
something more OO-ish, while trying to avoid the complexity of most OO
languages, expanding in bite-sized chunks.

Doing things incrementally is exactly how you get "the complexity of
most OO languages". It's exactly why JavaScript has many different ways
to create objects, why C++ has new and delete and std::unique_ptr and
std::shared_ptr and multiple inheritance and virtual inheritance and...

As for familiarity and similarity to other languages... What I've
called objects in OpenSCAD are patterned after JavaScript objects. 
Their access mechanisms are nearly identical to JS objects.[1]  With
the OEP8 syntax (PR #6081), the creation syntax is a superset of JS's
syntax.[2][3]

You can't straightforwardly translate almost any actual JavaScript code
to OpenSCAD because it mutates objects. I think that makes it a poor
example to copy. My (and many others') complaints about Go is they
ignored almost all of 50 years of language design and are now catching
up in a worse and uglier way (i.e. generics). Having compiler-enforced
Result and Option instead of multiple return values would have prevented
many real bugs I found in my coworkers' code (and, okay, occasionally
mine :-D).

Like JS's mechanism, calling shadowed parent methods is ...
interesting.  (I should note that for work-legacy reasons almost all
of my JS experience is with 2006-era JS.)  I have two thoughts on the
subject.  First, when you clone the original object, you can copy a
method reference into your own name, preserving access to it:

 o1 = object(method=function (arg) ... );
 o2 = object(o1, superMethod=o1.method, method=function (arg) this.superMethod(arg) + 1);

This is definitely not simple.

  • Cory Cross

That will work without any additional built-in mechanisms (beyond
"this", of course).

Alternatively, if there's a mechanism that allows calling a function
and handing it a "this" (JavaScript's Function.call), you can use
that, combined with access to the "parent" object, to call a parent
method.  Handwaving over the details of that mechanism, and using JS's
mechanism for the moment, you would do something like:

 o2 = object(o1, method=function (arg) o1.method.call(this, arg) + 1);

Whether a library uses the builder pattern or some other style is up
to the library.  One of the other enhancements I'd like to see added
is varargs mechanisms akin to Python's *args and **kwargs mechanisms. 
Those mechanisms could be used to avoid duplicated argument parsing in
a library that wants to stick with the named-argument pattern.

I thought there was more that I wanted to write about, but I've lost
track.


OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org

On 8/23/25 1:37 PM, Jordan Brown via Discuss wrote: > Cory says that inheritance is a "day one" item for learning Python.  I > don't happen to agree, but I think for OpenSCAD it is *critical* that > basically nothing associated with objects is a day-one item, or even a > month-one item. My point was that anyone who knows anything about objects and programming is going to expect inheritance if you say methods and object, not that someone who learns OpenSCAD needs to know inheritance to model. You call it a dictionary and say it has scoped functions, they're going to think "huh". > As they stand today, they are indeed better mapped to Python > dictionaries.  We could call them dictionaries, records, or any of a > dozen other data-only names.  But then when and if we add more OO-like > features, starting with Peter's work on a "this" mechanism, we would > be stuck forever with a name that is inappropriate... > My suggestion is that we should not add any OO-like features to the key-value store unless we've thought through the design. > or, worse, with two similar-but-different concepts with different > names, like Python has.  My hope is to expand into supporting > something more OO-ish, while trying to avoid the complexity of most OO > languages, expanding in bite-sized chunks. > Doing things incrementally is exactly how you get "the complexity of most OO languages". It's exactly why JavaScript has many different ways to create objects, why C++ has new and delete and std::unique_ptr and std::shared_ptr and multiple inheritance and virtual inheritance and... > As for familiarity and similarity to other languages... What I've > called objects in OpenSCAD are patterned after JavaScript objects.  > Their access mechanisms are nearly identical to JS objects.[1]  With > the OEP8 syntax (PR #6081), the creation syntax is a superset of JS's > syntax.[2][3] > You can't straightforwardly translate almost any actual JavaScript code to OpenSCAD because it mutates objects. I think that makes it a poor example to copy. My (and many others') complaints about Go is they ignored almost all of 50 years of language design and are now catching up in a worse and uglier way (i.e. generics). Having compiler-enforced Result and Option instead of multiple return values would have prevented many real bugs I found in my coworkers' code (and, okay, occasionally mine :-D). > Like JS's mechanism, calling shadowed parent methods is ... > interesting.  (I should note that for work-legacy reasons almost all > of my JS experience is with 2006-era JS.)  I have two thoughts on the > subject.  First, when you clone the original object, you can copy a > method reference into your own name, preserving access to it: > > o1 = object(method=function (arg) ... ); > o2 = object(o1, superMethod=o1.method, method=function (arg) this.superMethod(arg) + 1); > This is definitely not simple. - Cory Cross > That will work without any additional built-in mechanisms (beyond > "this", of course). > > Alternatively, if there's a mechanism that allows calling a function > and handing it a "this" (JavaScript's Function.call), you can use > that, combined with access to the "parent" object, to call a parent > method.  Handwaving over the details of that mechanism, and using JS's > mechanism for the moment, you would do something like: > > o2 = object(o1, method=function (arg) o1.method.call(this, arg) + 1); > > > Whether a library uses the builder pattern or some other style is up > to the library.  One of the other enhancements I'd like to see added > is varargs mechanisms akin to Python's *args and **kwargs mechanisms.  > Those mechanisms could be used to avoid duplicated argument parsing in > a library that wants to stick with the named-argument pattern. > > > I thought there was more that I wanted to write about, but I've lost > track. > > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email todiscuss-leave@lists.openscad.org
JB
Jordan Brown
Sun, Aug 24, 2025 11:17 PM

As they stand today, they are indeed better mapped to Python
dictionaries.  We could call them dictionaries, records, or any of a
dozen other data-only names.  But then when and if we add more
OO-like features, starting with Peter's work on a "this" mechanism,
we would be stuck forever with a name that is inappropriate...

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?

Doing things incrementally is exactly how you get "the complexity of
most OO languages". It's exactly why JavaScript has many different
ways to create objects, why C++ has new and delete and std::unique_ptr
and std::shared_ptr and multiple inheritance and virtual inheritance
and...

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.

As for familiarity and similarity to other languages... What I've
called objects in OpenSCAD are patterned after JavaScript objects. 
Their access mechanisms are nearly identical to JS objects.[1]  With
the OEP8 syntax (PR #6081), the creation syntax is a superset of JS's
syntax.[2][3]

You can't straightforwardly translate almost any actual JavaScript
code to OpenSCAD because it mutates objects. I think that makes it a
poor example to copy.

Depending on what you mean by "mutates objects", any mainstream-ish
language with objects mutates them.

I didn't say that you could translate JS programs.  I said that it was
similar.

And JS is, according to several surveys, the world's most commonly-used
programming language, making it familiar to more people than any other
option.

Like JS's mechanism, calling shadowed parent methods is ...
interesting.  (I should note that for work-legacy reasons almost all
of my JS experience is with 2006-era JS.)  I have two thoughts on the
subject.  First, when you clone the original object, you can copy a
method reference into your own name, preserving access to it:

 o1 = object(method=function (arg) ... );
 o2 = object(o1, superMethod=o1.method, method=function (arg) this.superMethod(arg) + 1);

This is definitely not simple.

And if I thought that we were going to have most users building class
hierarchies, that would bother me.  But we're not.

My expectation is that most users will never consciously create an
object with methods.

Doing something like Function.call() is in many ways cleaner, and
perhaps we should eventually do that.

>> As they stand today, they are indeed better mapped to Python >> dictionaries.  We could call them dictionaries, records, or any of a >> dozen other data-only names.  But then when and if we add more >> OO-like features, starting with Peter's work on a "this" mechanism, >> we would be stuck forever with a name that is inappropriate... >> > 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? > Doing things incrementally is exactly how you get "the complexity of > most OO languages". It's exactly why JavaScript has many different > ways to create objects, why C++ has new and delete and std::unique_ptr > and std::shared_ptr and multiple inheritance and virtual inheritance > and... 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. >> As for familiarity and similarity to other languages... What I've >> called objects in OpenSCAD are patterned after JavaScript objects.  >> Their access mechanisms are nearly identical to JS objects.[1]  With >> the OEP8 syntax (PR #6081), the creation syntax is a superset of JS's >> syntax.[2][3] >> > You can't straightforwardly translate almost any actual JavaScript > code to OpenSCAD because it mutates objects. I think that makes it a > poor example to copy. Depending on what you mean by "mutates objects", any mainstream-ish language with objects mutates them. I didn't say that you could translate JS programs.  I said that it was similar. And JS is, according to several surveys, the world's most commonly-used programming language, making it familiar to more people than any other option. >> Like JS's mechanism, calling shadowed parent methods is ... >> interesting.  (I should note that for work-legacy reasons almost all >> of my JS experience is with 2006-era JS.)  I have two thoughts on the >> subject.  First, when you clone the original object, you can copy a >> method reference into your own name, preserving access to it: >> >> o1 = object(method=function (arg) ... ); >> o2 = object(o1, superMethod=o1.method, method=function (arg) this.superMethod(arg) + 1); >> > This is definitely not simple. And if I thought that we were going to have most users building class hierarchies, that would bother me.  But we're not. My expectation is that most users will never consciously create an object with methods. Doing something like Function.call() is in many ways cleaner, and perhaps we should eventually do that.
CC
Cory Cross
Mon, Aug 25, 2025 12:23 AM

On 8/24/25 4:17 PM, Jordan Brown via Discuss wrote:

As they stand today, they are indeed better mapped to Python
dictionaries.  We could call them dictionaries, records, or any of a
dozen other data-only names.  But then when and if we add more
OO-like features, starting with Peter's work on a "this" mechanism,
we would be stuck forever with a name that is inappropriate...

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

As for familiarity and similarity to other languages... What I've
called objects in OpenSCAD are patterned after JavaScript objects. 
Their access mechanisms are nearly identical to JS objects.[1]  With
the OEP8 syntax (PR #6081), the creation syntax is a superset of
JS's syntax.[2][3]

You can't straightforwardly translate almost any actual JavaScript
code to OpenSCAD because it mutates objects. I think that makes it a
poor example to copy.

Depending on what you mean by "mutates objects", any mainstream-ish
language with objects mutates them.

I didn't say that you could translate JS programs.  I said that it was
similar.

And JS is, according to several surveys, the world's most
commonly-used programming language, making it familiar to more people
than any other option.

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.

This is definitely not simple.

And if I thought that we were going to have most users building class
hierarchies, that would bother me.  But we're not.

My expectation is that most users will never consciously create an
object with methods.

Doing something like Function.call() is in many ways cleaner, and
perhaps we should eventually do that.

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.

/back to C++

  • Cory
On 8/24/25 4:17 PM, Jordan Brown via Discuss wrote: >>> >>> As they stand today, they are indeed better mapped to Python >>> dictionaries.  We could call them dictionaries, records, or any of a >>> dozen other data-only names.  But then when and if we add more >>> OO-like features, starting with Peter's work on a "this" mechanism, >>> we would be stuck forever with a name that is inappropriate... >>> >> 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 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>., 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. >>> As for familiarity and similarity to other languages... What I've >>> called objects in OpenSCAD are patterned after JavaScript objects.  >>> Their access mechanisms are nearly identical to JS objects.[1]  With >>> the OEP8 syntax (PR #6081), the creation syntax is a superset of >>> JS's syntax.[2][3] >>> >> You can't straightforwardly translate almost any actual JavaScript >> code to OpenSCAD because it mutates objects. I think that makes it a >> poor example to copy. > > Depending on what you mean by "mutates objects", any mainstream-ish > language with objects mutates them. > > I didn't say that you could translate JS programs.  I said that it was > similar. > > And JS is, according to several surveys, the world's most > commonly-used programming language, making it familiar to more people > than any other option. > 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. >> This is definitely not simple. > > And if I thought that we were going to have most users building class > hierarchies, that would bother me.  But we're not. > > My expectation is that most users will never consciously create an > object with methods. > > Doing something like Function.call() is in many ways cleaner, and > perhaps we should eventually do that. > 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. /back to C++ - Cory