if(false) returns empty object in RC3, breaks code from 2019.05

classic Classic list List threaded Threaded
23 messages Options
12
Reply | Threaded
Open this post in threaded view
|

if(false) returns empty object in RC3, breaks code from 2019.05

adrianv
I recall this being discussed a while back and I'm not sure about what the
ultimate conclusion was, and I can't remember what the issue was that gave
rise to the change.  But it seems like the RC3 version has the behavior that
if(false) returns an empty object rather than nothing.  This is different
from what happened in 2019.05, it seems, as shown by this example which
breaks in RC3 but works in 2019.  

chamfer = false;   // Set to true to get chamfer
intersection(){
   cube(10);
   if (chamfer)
      translate([0,-10,12]) rotate([-45,0,0])cube(30);
}

When chamfer is false I expect a cube but instead I get nothing because the
intersection is with an empty object.  Note that in the list context:
[1,2,if (false) 27, 3, 4] the conditional produces nothing, not an empty
list, so the new behavior makes the openscad language less internally
consistent.  Is there some advantage of the new behavior?  Note that if this
change is staying, it should be highlighted as a language change from the
2019 version.  




--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

JordanBrown
I don't think it returns an empty object in 2019.05.  (Check the CSG tree.)  What I think is happening is that intersection in 2019.05 ignores the empty object.

Having it return nothing would break things in a different way.
want_sphere = false;

module foo() {
    children(1);
}

foo() {
    cube();
    if (want_sphere) sphere();
    cylinder();
}

should emit either a sphere or nothing, depending on how want_sphere is set.


  


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

adrianv




--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

JordanBrown
In reply to this post by JordanBrown
On 12/25/2020 12:12 PM, Jordan Brown wrote:
I don't think it returns an empty object in 2019.05.  (Check the CSG tree.)

I mis-wrote.  I don't think it returns *nothing* in 2019.05.  In both 2019.05 and the current build, if(false) yields an empty group() in the CSG tree.  As do echo(), for()-that-never-runs, and empty modules.

The difference is in what intersection() and difference() do with children that don't contain any solid-generators.

2019.05's difference() and intersection() ignore children that don't contain anything that generates a solid.
Thus echo, if(false), "translate([0,0,0]);", empty modules, et cetera are all ignored.
An intersection or difference of a cube or other solid, that yields nothing, is *not* ignored.

The current build respects those "super empty" structures.

What I think is happening is that intersection in 2019.05 ignores the empty object.

Having it return nothing would break things in a different way.
want_sphere = false;

module foo() {
    children(1);
}

foo() {
    cube();
    if (want_sphere) sphere();
    cylinder();
}

should emit either a sphere or nothing, depending on how want_sphere is set.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

adrianv
How do you view the CSG tree?  

I noticed that the example you posted works the same in both versions, so
that does imply that it returns an empty object and that the question is
maybe what intersection or difference do with empty objects.  Here's a
simple example:
 
difference(){
    union() {};
    cube();
}    

The above code produces a cube in 2019.05 and nothing in RC3.   To me the
old behavior makes more sense: if you haven't specified an object then
ignore it rather than "no object" being the same as the empty set.  

I could see leaving the current behavior but introducing a nothing() object
that lets you not return geometry.  I could also see changing difference and
intersection to the old behavior and having a nothing() object that acts
like the empty set.  But maybe this second case doesn't work because the
change fixed a bug?   It seems like "if" is a weaker tool if it always has
to return geometry that is acted upon.



JordanBrown wrote

> On 12/25/2020 12:12 PM, Jordan Brown wrote:
>> I don't think it returns an empty object in 2019.05.  (Check the CSG
>> tree.)
>
> I mis-wrote.  I don't think it returns *nothing* in 2019.05.  In both
> 2019.05 and the current build, if(false) yields an empty group() in the
> CSG tree.  As do echo(), for()-that-never-runs, and empty modules.
>
> The difference is in what intersection() and difference() do with
> children that don't contain any solid-generators.
>
> 2019.05's difference() and intersection() ignore children that don't
> contain anything that generates a solid.
> Thus echo, if(false), "translate([0,0,0]);", empty modules, et cetera
> are all ignored.
> An intersection or difference of a cube or other solid, that yields
> nothing, is *not* ignored.
>
> The current build respects those "super empty" structures.
>
>> What I think is happening is that intersection in 2019.05 ignores the
>> empty object.
>>
>> Having it return nothing would break things in a different way.
>>
>>     want_sphere = false;
>>
>>     module foo() {
>>         children(1);
>>     }
>>
>>     foo() {
>>         cube();
>>         if (want_sphere) sphere();
>>         cylinder();
>>     }
>>
>> should emit either a sphere or nothing, depending on how want_sphere
>> is set.
>>
>>
>> _______________________________________________
>> OpenSCAD mailing list
>>

> Discuss@.openscad

>> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
>
>
> _______________________________________________
> OpenSCAD mailing list

> Discuss@.openscad

> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org





--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

MichaelAtOz
Administrator
adrianv wrote
> How do you view the CSG tree?  

Design/Display-CSG-tree



-----
OpenSCAD Admin - email* me if you need anything,  or if I've done something stupid...

* on the Forum, click on my MichaelAtOz label, there is a link to email me.

Unless specifically shown otherwise above, my contribution is in the Public Domain; to the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. Obviously inclusion of works of previous authors is not included in the above.

--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
OpenSCAD Admin - email* me if you need anything, or if I've done something stupid...
* on the Forum, click on my MichaelAtOz label, there is a link to email me.

Unless specifically shown otherwise above, my contribution is in the Public Domain;
to the extent possible under law, I have waived all copyright and related or neighbouring rights to this work.
Obviously inclusion of works of previous authors is not included in the above.
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

adrianv
In reply to this post by JordanBrown
Now that I know how to look at CSG trees I did some inspection.  

It looks like some objects have changed behavior.  

echo and assert give group(); in 2019.05 but give nothing in RC3.

if(false) and for(never) give group(); in both versions.  

And I discovered something really bad:  it's now a warning to write a for
statement that doesn't run.  This is a terrible idea.  It's going to break a
bunch of base cases and degenerate cases all over the place.  




JordanBrown wrote

> On 12/25/2020 12:12 PM, Jordan Brown wrote:
>> I don't think it returns an empty object in 2019.05.  (Check the CSG
>> tree.)
>
> I mis-wrote.  I don't think it returns *nothing* in 2019.05.  In both
> 2019.05 and the current build, if(false) yields an empty group() in the
> CSG tree.  As do echo(), for()-that-never-runs, and empty modules.
>
> The difference is in what intersection() and difference() do with
> children that don't contain any solid-generators.
>
> 2019.05's difference() and intersection() ignore children that don't
> contain anything that generates a solid.
> Thus echo, if(false), "translate([0,0,0]);", empty modules, et cetera
> are all ignored.
> An intersection or difference of a cube or other solid, that yields
> nothing, is *not* ignored.
>
> The current build respects those "super empty" structures.
>
>> What I think is happening is that intersection in 2019.05 ignores the
>> empty object.
>>
>> Having it return nothing would break things in a different way.
>>
>>     want_sphere = false;
>>
>>     module foo() {
>>         children(1);
>>     }
>>
>>     foo() {
>>         cube();
>>         if (want_sphere) sphere();
>>         cylinder();
>>     }
>>
>> should emit either a sphere or nothing, depending on how want_sphere
>> is set.
>>
>>
>> _______________________________________________
>> OpenSCAD mailing list
>>

> Discuss@.openscad

>> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
>
>
> _______________________________________________
> OpenSCAD mailing list

> Discuss@.openscad

> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org





--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

nophead
I think it is only a warning in for() if the range is literal. So a loop that will never run with any input variables is a warning but one that sometimes doesn't run is OK.

On Sat, 26 Dec 2020 at 00:14, adrianv <[hidden email]> wrote:
Now that I know how to look at CSG trees I did some inspection. 

It looks like some objects have changed behavior. 

echo and assert give group(); in 2019.05 but give nothing in RC3.

if(false) and for(never) give group(); in both versions. 

And I discovered something really bad:  it's now a warning to write a for
statement that doesn't run.  This is a terrible idea.  It's going to break a
bunch of base cases and degenerate cases all over the place. 




JordanBrown wrote
> On 12/25/2020 12:12 PM, Jordan Brown wrote:
>> I don't think it returns an empty object in 2019.05.  (Check the CSG
>> tree.)
>
> I mis-wrote.  I don't think it returns *nothing* in 2019.05.  In both
> 2019.05 and the current build, if(false) yields an empty group() in the
> CSG tree.  As do echo(), for()-that-never-runs, and empty modules.
>
> The difference is in what intersection() and difference() do with
> children that don't contain any solid-generators.
>
> 2019.05's difference() and intersection() ignore children that don't
> contain anything that generates a solid.
> Thus echo, if(false), "translate([0,0,0]);", empty modules, et cetera
> are all ignored.
> An intersection or difference of a cube or other solid, that yields
> nothing, is *not* ignored.
>
> The current build respects those "super empty" structures.
>
>> What I think is happening is that intersection in 2019.05 ignores the
>> empty object.
>>
>> Having it return nothing would break things in a different way.
>>
>>     want_sphere = false;
>>
>>     module foo() {
>>         children(1);
>>     }
>>
>>     foo() {
>>         cube();
>>         if (want_sphere) sphere();
>>         cylinder();
>>     }
>>
>> should emit either a sphere or nothing, depending on how want_sphere
>> is set.
>>
>>
>> _______________________________________________
>> OpenSCAD mailing list
>>

> Discuss@.openscad

>> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
>
>
> _______________________________________________
> OpenSCAD mailing list

> Discuss@.openscad

> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org





--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

thehans
In reply to this post by adrianv

On Fri, Dec 25, 2020 at 10:14 AM adrianv <[hidden email]> wrote:
I recall this being discussed a while back and I'm not sure about what the
ultimate conclusion was, and I can't remember what the issue was that gave
rise to the change.

which was a fix for at least 4 existing issues:

On Fri, Dec 25, 2020 at 2:38 PM Jordan Brown <[hidden email]> wrote:
On 12/25/2020 12:12 PM, Jordan Brown wrote:
I don't think it returns an empty object in 2019.05.  (Check the CSG tree.)

I mis-wrote.  I don't think it returns *nothing* in 2019.05.  In both 2019.05 and the current build, if(false) yields an empty group() in the CSG tree.  As do echo(), for()-that-never-runs, and empty modules.

The difference is in what intersection() and difference() do with children that don't contain any solid-generators.

2019.05's difference() and intersection() ignore children that don't contain anything that generates a solid.
Thus echo, if(false), "translate([0,0,0]);", empty modules, et cetera are all ignored.
An intersection or difference of a cube or other solid, that yields nothing, is *not* ignored.

 Jordan is correct here, the old version basically had no concept of empty geometry and treated it the same as "no geometry" (eg an echo or assert node without children).  
So I guess the issue is now whether or not an untaken conditional should be counted as empty geometry or not geometry at all.

Without enabling the experimental feature of "lazy-union", a group essentially implies a union.  So an empty group is treated much like "union() {}" which means "the empty set" of geometry.

Hans


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

nophead
I think a false if or an empty for() should not create empty geometry, just as echo() doesn't. It is more backwards compatible and you can always force empty geometry with else union();

On Sat, 26 Dec 2020 at 14:40, Hans L <[hidden email]> wrote:

On Fri, Dec 25, 2020 at 10:14 AM adrianv <[hidden email]> wrote:
I recall this being discussed a while back and I'm not sure about what the
ultimate conclusion was, and I can't remember what the issue was that gave
rise to the change.

which was a fix for at least 4 existing issues:

On Fri, Dec 25, 2020 at 2:38 PM Jordan Brown <[hidden email]> wrote:
On 12/25/2020 12:12 PM, Jordan Brown wrote:
I don't think it returns an empty object in 2019.05.  (Check the CSG tree.)

I mis-wrote.  I don't think it returns *nothing* in 2019.05.  In both 2019.05 and the current build, if(false) yields an empty group() in the CSG tree.  As do echo(), for()-that-never-runs, and empty modules.

The difference is in what intersection() and difference() do with children that don't contain any solid-generators.

2019.05's difference() and intersection() ignore children that don't contain anything that generates a solid.
Thus echo, if(false), "translate([0,0,0]);", empty modules, et cetera are all ignored.
An intersection or difference of a cube or other solid, that yields nothing, is *not* ignored.

 Jordan is correct here, the old version basically had no concept of empty geometry and treated it the same as "no geometry" (eg an echo or assert node without children).  
So I guess the issue is now whether or not an untaken conditional should be counted as empty geometry or not geometry at all.

Without enabling the experimental feature of "lazy-union", a group essentially implies a union.  So an empty group is treated much like "union() {}" which means "the empty set" of geometry.

Hans

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

adrianv
I think it's a strong argument that you can always insert empty geometry if
you need it, but there's no way to remove empty geometry once it's been
created, so I agree with nophead that false if and empty for should behave
like echo and assert and not create any geometry.  Even though Jordan
pointed out how either choice can break backwards compatibility, my feeling
is that the current version (where for and if generate empty geometry)
breaks more things than the alternative, and the fixes for those broken
things are more painful, since you have to repeat blocks of code instead of
just adding an "else union();"


nophead wrote
> I think a false if or an empty for() should not create empty geometry,
> just
> as echo() doesn't. It is more backwards compatible and you can always
> force
> empty geometry with else union();
>
> On Sat, 26 Dec 2020 at 14:40, Hans L &lt;

> thehans@

> &gt; wrote:
>
>>
>> On Fri, Dec 25, 2020 at 10:14 AM adrianv &lt;

> avm4@

> &gt; wrote:
>>
>>> I recall this being discussed a while back and I'm not sure about what
>>> the
>>> ultimate conclusion was, and I can't remember what the issue was that
>>> gave
>>> rise to the change.
>>>
>>
>> The change was introduced by
>> https://github.com/openscad/openscad/pull/3342
>> which was a fix for at least 4 existing issues:
>> https://github.com/openscad/openscad/issues/666
>> https://github.com/openscad/openscad/issues/3311
>> https://github.com/openscad/openscad/issues/3312
>> https://github.com/openscad/openscad/issues/3416
>>
>> On Fri, Dec 25, 2020 at 2:38 PM Jordan Brown <
>>

> openscad@.maileater

>> wrote:
>>
>>> On 12/25/2020 12:12 PM, Jordan Brown wrote:
>>>
>>> I don't think it returns an empty object in 2019.05.  (Check the CSG
>>> tree.)
>>>
>>>
>>> I mis-wrote.  I don't think it returns *nothing* in 2019.05.  In both
>>> 2019.05 and the current build, if(false) yields an empty group() in the
>>> CSG
>>> tree.  As do echo(), for()-that-never-runs, and empty modules.
>>>
>>> The difference is in what intersection() and difference() do with
>>> children that don't contain any solid-generators.
>>>
>>> 2019.05's difference() and intersection() ignore children that don't
>>> contain anything that generates a solid.
>>> Thus echo, if(false), "translate([0,0,0]);", empty modules, et cetera
>>> are
>>> all ignored.
>>> An intersection or difference of a cube or other solid, that yields
>>> nothing, is *not* ignored.
>>>
>>
>>  Jordan is correct here, the old version basically had no concept of
>> empty
>> geometry and treated it the same as "no geometry" (eg an echo or assert
>> node without children).
>> So I guess the issue is now whether or not an untaken conditional should
>> be counted as empty geometry or not geometry at all.
>>
>> Without enabling the experimental feature of "lazy-union", a group
>> essentially implies a union.  So an empty group is treated much like
>> "union() {}" which means "the empty set" of geometry.
>>
>> Hans
>>
>> _______________________________________________
>> OpenSCAD mailing list
>>

> Discuss@.openscad

>> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
>>
>
> _______________________________________________
> OpenSCAD mailing list

> Discuss@.openscad

> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org





--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

JordanBrown
In reply to this post by JordanBrown
On 12/25/2020 12:38 PM, Jordan Brown wrote:
An intersection or difference of a cube or other solid, that yields nothing, is *not* ignored.

Correction:  I swear that I tested that, but today I test it and 2019.05 *does* ignore such an empty set.

This yields two cubes:
module nothing1() {
}

module nothing2() {
    intersection() {
        cube();
        translate([10,0,0]) cube();
    }
}

intersection() {
    cube();
    nothing1();
}

translate([20,0,0]) {
    intersection() {
        cube();
        nothing2();
    }
}


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

JordanBrown
In reply to this post by adrianv
[ I'm not sure whether my commentary below really adds value.  However, it helped me to work through some of the issues and think about the issues in a somewhat structured way, so I'll send it out.  The TL;DR version is that there probably needs to be a kind of nothingness that is between "absolutely nothing" and "an empty set".  ]

On 12/26/2020 7:02 AM, adrianv wrote:
I think it's a strong argument that you can always insert empty geometry if
you need it, but there's no way to remove empty geometry once it's been
created, so I agree with nophead that false if and empty for should behave
like echo and assert and not create any geometry.  Even though Jordan
pointed out how either choice can break backwards compatibility, my feeling
is that the current version (where for and if generate empty geometry)
breaks more things than the alternative, and the fixes for those broken
things are more painful, since you have to repeat blocks of code instead of
just adding an "else union();" 

It's hard to talk about different kinds of "nothing", and terms like "object" are overloaded, so I'll define a couple of terms for what I'm about to say:

When I say "nothing", it means that there is absolutely nothing generated.  Comments generate nothing.  Assignments generate nothing.  The program "", the program consisting of no text at all, generates nothing.

When I say "set", I mean a (possibly empty) collection of points.

When I say "empty set", it means that there are operations, but that the result contains no points.  Examples include union() {} and intersection() { cube(); translate([10,0,0]) cube() }.

When I say "module-invocation-like construct", I mean anything that looks like "xxx(...);" or "xxx(...) { ... }".  As a special case, I include "if(...) { ... } else { ... }".

---  I think the next section describes fact, not opinion. ---

In 2019.05:
  • Every module-invocation-like construct generates exactly one set.  There is no module-invocation-like construct that will generate nothing.  In particular, echo(), assert(), and failing if() yield empty sets.
  • intersection, difference, and intersection_for ignore children that are empty sets.  Thus, unlike in mathematics, the intersection of X and an empty set is X.  If E is an empty set, difference() { E; Y; Z; } is the same as difference() { Y; Z; }.

In RC3:

  • echo() and assert() yield nothing.  Except:  they count as children for purposes of $children and children().
  • failing if() yields an empty set.
  • Successful if() yields at least an empty set, even if its contents yield nothing.
  • A module yields at least an empty set, even if its contents yield nothing.
  • intersection and difference respect children that are empty sets.  As in mathematics, the intersection of an empty set and anything is an empty set.  The difference of an empty set and anything is an empty set.

Note that RC3 has made two changes:

  • intersection and difference respect empty sets.
  • There are now module-invocation-like constructs that yield nothing - echo() and assert().

--- Now for opinion ---

I think that the change to intersection and difference is correct.  It matches mathematics, and it avoids perverse results in (admittedly contrived) cases.  For instance, in 2019.05:

module foo(d) {
    intersection() {
        cube();
        intersection() {
            cube();
            translate([d,0,0]) cube();
        }
    }
}

will yield part of a cube if d is -1 through 1, but will yield a full cube if d is outside that range.  In particular, foo(0.999) will yield a very thin slice of a cube, but foo(1.001) will yield a full cube.  (The behavior of foo(1) is left as a question for philosophers.)


I have mixed feelings about the change to echo() and assert().  On the one hand, it's intuitively sensible.  These are clearly not attempts to describe geometry.  On the other hand, they introduce perverse cases.  echo() produces nothing, but if(true)echo() produces an empty set.  A module consisting only of an invocation of echo() produces an empty set.  children(i) where the specified child is an echo yields nothing.

Yeah, I'll have to come down on the side of "no" on that one.  I can't even find a clear way to describe the behavior of echo().  It's not nothing, but it's not an empty set.  (But see below for an idea.)


As for the "failing if yields nothing" proposal, I have to come down solidly against.  (But again, see below.) Although again it's intuitively sensible, it makes a hash of what $children means and the indexing of children.  What does this print?  (Note that rands(0,1,1)[0] yields a random number between 0 and 1.)

module count() {
    echo($children);
    echo($children);
}

count() {
    if (rands(0,1,1)[0] > 0.5) {
        cube();
    }
}

Before "failing if yields nothing", you don't need to evaluate the children to determine the value of $children.  With it, you have to evaluate them to determine whether or not they generate nothing.  And if you recursively apply nothingness (so that an if whose body yields nothing, yields nothing, and so a module whose body yields nothing, yields nothing) then you have to evaluate arbitrarily deeply. What if some of that evaluation is erroneous when done in the wrong context?

value = -1;

module foo() {
    echo($children);
    if (value >= 0) {
        children(0);
    }
}

function safe_sqrt(v) = assert(v >= 0) sqrt(v);

foo() {
    if (safe_sqrt(value) > 1) {
        cube();
    }
}

When do you calculate the value of $children?  Before doing anything with the module?  What if the children's behavior depends on $ variables set by the module?  Do you re-evaluate $children each time it's used?  Do you re-evaluate the child list each time children() is used?

module perverse() {
    $mychildren = $children;
    echo($children);
}

perverse() {
    if ($mychildren == 0) {
        cube();
    }
}

That's purposely perverse, but I've had programs that had children that conditionally supplied geometry depending on $ variables set by the parent.

module foo() {
    for ($i=[0:3]) {
        children();
    }
}

foo() {
    if ($i != 0) {
        cube();
    }
}
---

The best idea I come up with to comply with some of the intuitively desirable behavior of echo() and failing if() is to define a new kind of nothingness.  Call it NULL.
  • echo() and failing if(), among others, would yield NULL.
  • NULL is not nothing.  It counts as a child.  (So all module-like invocations yield children, avoiding the $children problem described above.)
  • Intersection and difference would ignore NULL children.
  • Ideally, many operations on NULL would yield NULL.
    • A module whose contents all yield NULL (or with no contents at all) would yield NULL.
    • An "if" whose children all yield NULL (or with no children) would yield NULL.
    • children(i), where the corresponding child is NULL, would yield NULL.
  • However, boolean operations on NULL should probably yield empty sets.  In particular, union() {} should yield an empty set.  intersection() { NULL } would ignore the NULL child, and would then yield an empty set.
That seems to address most of the concerns.  The fact that echo() counts as a child might cause confusion for some.

(Note:  I suspect that PR#3555 may effectively implement some of this, though defined in terms of counting nodes in the AST tree for $children and children() purposes.)

HOWEVER, I'll point out that "null" and similar concepts have been very difficult subjects in other languages.  C programmers often mess up the difference between NULL and empty strings.  JavaScript programmers are treated to something like five different kinds of nothingness - undefined, null, {}, [], and "" - and confusion involving them is common.  Programmers in many languages often have trouble with the idea of arrays with no entries, without even bringing in the concept of NULL.


    

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

adrianv
I agree that operations should interact correctly with empty sets.  

Current behavior of RC3 is that echo() and assert() return your NULL.  They
behave like children but generate no geometry.  Note that echo is only
actually run if the child is actually instantiated.  

    module foo() {
        intersection(){
          children(0);
          children(1);
        }
    }

    foo() {
        cube();
        echo("child 1");
        cylinder();
   
}

Above code produces a cube and displays "child 1".  If you swap the echo and
the cylinder then you get the intersection of cube with cylinder and nothing
is printed.  This is arguably somewhat confusing.  Consider this:

    foo() {
        cube();
        cylinder();
        assert(false, "fail!");
}

The assert never runs.  Also arguably confusing.  But this behavior is
consistent between versions.   It seems like the truth is that passing
assert or echo as children is just weird and confusing.  Is there any use in
doing this?  

My proposal, which is consistent with your suggestion, is that false if and
empty for should behave the same as echo, namely that they behave as your
NULL, which already exists, like it or not.  And I think the behavior of
echo (and assert) is far more confusing than my proposed behavior for if()
and for() would be.  

However, in thinking carefully about this I wonder if any of the options
really avoids confusion.  It seems like the main case where this matters
with the standard language and with empty if or for is for intersection(),
because an actual empty object eliminates your model, and there's no clean
way to fix it.  With union or difference it doesn't matter.   (Is there any
way to add an "everything" object to openscad?)

But for user written modules, a variety of things may happen.  Consider a
module spread() that places its child i at position [10*i,0,0].  If you
include echo() as a child you get a blank space.  And the same thing happens
(in both language versions) if you pass "if (false) cube();"  This seems
like unexpected behavior to me.  But the only way around it is to give the
module the ability to identify modules as NULL.  And this starts to get
messy.  Most modules will want to count the non-null children and skip all
the NULL ones...which amounts to treating NULL children as nonexistent.
Does this end up any different than actually not passing the children at
all?


JordanBrown wrote

> The best idea I come up with to comply with some of the intuitively
> desirable behavior of echo() and failing if() is to define a new kind of
> nothingness.  Call it NULL.
>
>   * echo() and failing if(), among others, would yield NULL.
>   * NULL is not nothing.  It counts as a child.  (So all module-like
>     invocations yield children, avoiding the $children problem described
>     above.)
>   * Intersection and difference would ignore NULL children.
>   * Ideally, many operations on NULL would yield NULL.
>       o A module whose contents all yield NULL (or with no contents at
>         all) would yield NULL.
>       o An "if" whose children all yield NULL (or with no children)
>         would yield NULL.
>       o children(i), where the corresponding child is NULL, would yield
>         NULL.
>   * However, boolean operations on NULL should probably yield empty
>     sets.  In particular, union() {} should yield an empty set. 
>     intersection() { NULL } would ignore the NULL child, and would then
>     yield an empty set.
>
> That seems to address most of the concerns.  The fact that echo() counts
> as a child might cause confusion for some.
>
> (Note:  I suspect that PR#3555 may effectively implement some of this,
> though defined in terms of counting nodes in the AST tree for $children
> and children() purposes.)





--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

thehans
Keep in mind that echo and assert can both have children.  They only yield "NULL" if no geometry child nodes are given.  This was partly addressed for another issue: https://github.com/openscad/openscad/issues/3131 which is different since the 2019 release.
In the 2019 release, assert would render children if given, but echo would not.  Now they both do.
That's why I mentioned in my first post in this thread "(eg an echo or assert node **without children**).", [emphasis now added]  but may have been too subtle on that point.

So if you want to use echo or assert in a way that doesn't affect the child count then you can nest your actual geometry as children of them (probably preferably on the first element[s])

foo() {
        assert(false, "fail!") cube();
        cylinder();
}


On Sat, Dec 26, 2020 at 6:53 PM adrianv <[hidden email]> wrote:
I agree that operations should interact correctly with empty sets. 

Current behavior of RC3 is that echo() and assert() return your NULL.  They
behave like children but generate no geometry.  Note that echo is only
actually run if the child is actually instantiated. 

    module foo() {
        intersection(){
          children(0);
          children(1);
        }
    }

    foo() {
        cube();
        echo("child 1");
        cylinder();

}

Above code produces a cube and displays "child 1".  If you swap the echo and
the cylinder then you get the intersection of cube with cylinder and nothing
is printed.  This is arguably somewhat confusing.  Consider this:

    foo() {
        cube();
        cylinder();
        assert(false, "fail!");
}

The assert never runs.  Also arguably confusing.  But this behavior is
consistent between versions.   It seems like the truth is that passing
assert or echo as children is just weird and confusing.  Is there any use in
doing this?   

My proposal, which is consistent with your suggestion, is that false if and
empty for should behave the same as echo, namely that they behave as your
NULL, which already exists, like it or not.  And I think the behavior of
echo (and assert) is far more confusing than my proposed behavior for if()
and for() would be. 

However, in thinking carefully about this I wonder if any of the options
really avoids confusion.  It seems like the main case where this matters
with the standard language and with empty if or for is for intersection(),
because an actual empty object eliminates your model, and there's no clean
way to fix it.  With union or difference it doesn't matter.   (Is there any
way to add an "everything" object to openscad?)

But for user written modules, a variety of things may happen.  Consider a
module spread() that places its child i at position [10*i,0,0].  If you
include echo() as a child you get a blank space.  And the same thing happens
(in both language versions) if you pass "if (false) cube();"  This seems
like unexpected behavior to me.  But the only way around it is to give the
module the ability to identify modules as NULL.  And this starts to get
messy.  Most modules will want to count the non-null children and skip all
the NULL ones...which amounts to treating NULL children as nonexistent.
Does this end up any different than actually not passing the children at
all?


JordanBrown wrote
> The best idea I come up with to comply with some of the intuitively
> desirable behavior of echo() and failing if() is to define a new kind of
> nothingness.  Call it NULL.
>
>   * echo() and failing if(), among others, would yield NULL.
>   * NULL is not nothing.  It counts as a child.  (So all module-like
>     invocations yield children, avoiding the $children problem described
>     above.)
>   * Intersection and difference would ignore NULL children.
>   * Ideally, many operations on NULL would yield NULL.
>       o A module whose contents all yield NULL (or with no contents at
>         all) would yield NULL.
>       o An "if" whose children all yield NULL (or with no children)
>         would yield NULL.
>       o children(i), where the corresponding child is NULL, would yield
>         NULL.
>   * However, boolean operations on NULL should probably yield empty
>     sets.  In particular, union() {} should yield an empty set. 
>     intersection() { NULL } would ignore the NULL child, and would then
>     yield an empty set.
>
> That seems to address most of the concerns.  The fact that echo() counts
> as a child might cause confusion for some.
>
> (Note:  I suspect that PR#3555 may effectively implement some of this,
> though defined in terms of counting nodes in the AST tree for $children
> and children() purposes.)





--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

JordanBrown
In reply to this post by adrianv
[ Resend from correct address. ]

On 12/26/2020 4:52 PM, adrianv wrote:
Current behavior of RC3 is that echo() and assert() return your NULL.  They
behave like children but generate no geometry.

And intersection() in particular ignores children with no geometry.

Note that echo is only actually run if the child is actually instantiated.

Yes.  None of the children are run unless instantiated.  Violating that risks running them in cases where their parents know they won't work.

It seems like the truth is that passing assert or echo as children is just weird and confusing.

Somewhat.  But they obey the general rule:  they run when you say children(i) and refer to them.

Is there any use in doing this?

You might leave an echo as a placeholder for future work.
union() {
    part1();
    echo("need to write part 2");
}
Even for a selective parent, it might make sense.  Consider for instance a decorated_cube() module that builds a cube and and translates and rotates each of its six children to be on its faces.
decorated_cube() {
    face1();
    face2();
    echo("need to design face 3");
    ...
}
My proposal, which is consistent with your suggestion, is that false if and
empty for should behave the same as echo, namely that they behave as your
NULL,

Yes, that's what I was thinking.

which already exists, like it or not.

Well, in the RC, which is 95% existing but not 100%.  (I think my primary contribution to this discussion might be in giving it a name.)

And I think the behavior of echo (and assert) is far more confusing than my proposed behavior for if() and for() would be.  

Perhaps.  One of the things that was making my head hurt was that
echo("foo");

and

if (true) echo("foo");

currently behave differently in the RC, as children of intersection().  Similarly that a module that exists only to wrap around echo() behaves differently than a plain echo().

Even stranger is that plain echo generates NULL but

if (false) echo("foo");
generates an empty set.

However, in thinking carefully about this I wonder if any of the options
really avoids confusion.

Nope :-(.

It seems like the main case where this matters with the standard language and with empty if or for is for intersection(), because an actual empty object eliminates your model, and there's no clean way to fix it.

Intersecting with an *actual* empty-set object (say, the intersection of two disjoint objects) absolutely *should* eliminate your model.  YAFIYGI.  The question is what constructs should yield empty sets.

With union or difference it doesn't matter.

With difference it matters a little, because the first child is special (it's positive).
difference() {
    if (false);
    cube();
}

yields a cube in 2019.05 and an empty set in RC3.

But for user written modules, a variety of things may happen.  Consider a
module spread() that places its child i at position [10*i,0,0].

Yep.  Exactly the confusing case I was thinking of.

But the only way around it is to give the module the ability to identify modules as NULL.

But you can't know that it's NULL until you instantiate it, and then it's too late.

I think Very Bad Things(tm) will happen if you try to have any of these constructs not yield children.  The rule needs to be simple:  they get evaluated when and if their parents say to evaluate them.  You have to just understand that they *do* generate children.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

JordanBrown
In reply to this post by thehans
On 12/26/2020 6:12 PM, Hans L wrote:
Keep in mind that echo and assert can both have children.

You just made my head explode.  Now there are brains and blood all over my keyboard.

Concur that echo() and assert() should behave the same with respect to their children.  (Slight inclination towards "it's an error", but not enough to fight.)


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

adrianv
In reply to this post by JordanBrown
JordanBrown wrote
>> It seems like the truth is that passing assert or echo as children is
>> just weird and confusing.
>
> Somewhat.  But they obey the general rule:  they run when you say
> children(i) and refer to them.

Yes, everything makes sense if you understand the technical details.  But
for a casual user it's going to be surprising and puzzling, I think.  

The note that assert and echo take children is interesting.  It makes
sense...but I don't think I realized it.  (Even though that's how assert and
echo act in let() statements.  In fact, you have to use assert with children
in functions if you want to avoid dummy variables.  The same is true of
echo...but I usually only insert it for debugging so I'm not bothered by the
dummy vars.)  


>> Is there any use in doing this?
>
> You might leave an echo as a placeholder for future work.
>
>     union() {
>         part1();
>         echo("need to write part 2");
>     }

I think in my own work I'd be more inclined to stick a cube() or something
in as a placeholder than text.  Or just a comment.  


>> My proposal, which is consistent with your suggestion, is that false if
>> and
>> empty for should behave the same as echo, namely that they behave as your
>> NULL,
>
> Yes, that's what I was thinking.
>
>> which already exists, like it or not.
>
> Well, in the RC, which is 95% existing but not 100%.  (I think my
> primary contribution to this discussion might be in giving it a name.)

What is the missing 5%?  


>> And I think the behavior of echo (and assert) is far more confusing than
>> my proposed behavior for if() and for() would be.  
>
> Perhaps.  One of the things that was making my head hurt was that
>
>     echo("foo");
>
> and
>
>     if (true) echo("foo");
>
> currently behave differently in the RC, as children of intersection(). 
> Similarly that a module that exists only to wrap around echo() behaves
> differently than a plain echo().
>
> Even stranger is that plain echo generates NULL but
>
>     if (false) echo("foo");
>
> generates an empty set.

Wouldn't these things be "fixed" by having if statements not create
geometry?  


>> However, in thinking carefully about this I wonder if any of the options
>> really avoids confusion.
>
> Nope :-(.
>
>> It seems like the main case where this matters with the standard language
>> and with empty if or for is for intersection(), because an actual empty
>> object eliminates your model, and there's no clean way to fix it.
>
> Intersecting with an *actual* empty-set object (say, the intersection of
> two disjoint objects) absolutely *should* eliminate your model. 
> YAFIYGI.  The question is what constructs should yield empty sets.
>
>> With union or difference it doesn't matter.
>
> With difference it matters a little, because the first child is special
> (it's positive).
>
>     difference() {
>         if (false);
>         cube();
>     }
>
> yields a cube in 2019.05 and an empty set in RC3.

I'm in agreement that intersection with empty set should give the empty set.
The problem is that there's no way to specify the complement of the empty
set.  So I can't do

intersection(){
   if (condition) thing(); else everything();
   otherthing();
}

Being able to do that would solve the problem with intersections in a simple
manner.  I assume that it's impossible to implement the everything() object.  

For difference, yes, it matters what the first object is, and your example
does give a case where things change.  But it's not an example that seems
like it has a use case.  I can't think of a situation where you want A-B in
one case and B in another case.  It's just not likely.  It's also simple
because you've got only one possible meaningful condition, whether A is
enabled or not.  Consider an intersection like:

intersection(){  
   mainthing();
   if (parm1) thing1();
   if (parm2) thing2();
   if (parm3) thing3();
   if (parm4) thing4();
}

Now if I need to handle this it explodes into a real mess because I have to
consider every combination of the 4 variables separately, so I need 2^4=16
cases instead of 4 cases.  Presumably the only realistic way to solve this
is to write an "everything" module that is a superset of mainthing(), or do
something like

intersection(){
  mainthing();
  if (parm1) thing1(); else mainthing();
  if (parm2) thing2(); else mainthing();
  if (parm3) thing3(); else mainthing();
  if (parm4) thing4(); else mainthing();
}

Of course, the other solution is to avoid intersection() for cases like this
and use difference() instead.  Then there's no problem.    


>> But for user written modules, a variety of things may happen.  Consider a
>> module spread() that places its child i at position [10*i,0,0].
>
> Yep.  Exactly the confusing case I was thinking of.
>
>> But the only way around it is to give the module the ability to identify
>> modules as NULL.
>
> But you can't know that it's NULL until you instantiate it, and then
> it's too late.

Yeah.  At least this is not a change from the old behavior.  That is, the
spread module behaves the same in the RC3 as it does in the 2019 version.
In either case, the empty geometry or the NULL object get treated as a child
that renders as nothing, and you get a gap.   I'm left wondering if it
really matters whether if(false) and empty for return an empty set or a
NULL, since most of the time it will give the same result anyway.



--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

JordanBrown
[ Sigh.  Not my day.  First I sent this from the wrong address, then when I went to resend it from the right address I resent the one of Adrian's messages instead of this one. ]

On 12/26/2020 6:58 PM, adrianv wrote:
JordanBrown wrote
It seems like the truth is that passing assert or echo as children is
just weird and confusing.
Somewhat.  But they obey the general rule:  they run when you say
children(i) and refer to them.
Yes, everything makes sense if you understand the technical details.  But
for a casual user it's going to be surprising and puzzling, I think.

Yes.

What I'm saying is that if it's going to be surprising and puzzling, at least there should be a simple rule.

(And I think that all variations that have gotten past the first level of discussion have that simple rule, that all module-like invocations yield children.)

I think in my own work I'd be more inclined to stick a cube() or something in as a placeholder than text. Or just a comment.

A comment won't be enough if you're leaving a placeholder for child #3 of 6.

which already exists, like it or not.
Well, in the RC, which is 95% existing but not 100%.  (I think my
primary contribution to this discussion might be in giving it a name.)
What is the missing 5%?  

I wasn't saying that the functionality wasn't in the RC... but rather that the RC is an RC, not a release, so doesn't 100% exist.  Behavior in a non-release can change; behavior in a release should normally remain constant.

(But:  the RC has NULL functionality for echo, assert, intersection, and difference, but not for if, for, and modules.  Hans has a PR for if.)

[ if(true)echo() and if(false)echo() are different from echo() ]
Wouldn't these things be "fixed" by having if statements not create
geometry?  

Well, by having failing if not generate geometry - having it generate NULL - and by having successful if (or else) yield whatever its contents yield (which might or might not be NULL).

I'm in agreement that intersection with empty set should give the empty set.  The problem is that there's no way to specify the complement of the empty set. So I can't do
intersection(){
   if (condition) thing(); else everything();
   otherthing();
}

NULL would address this particular need, because a failing if would yield NULL, and intersection would be defined to ignore NULL.  So for intersection purposes, NULL would be sort of like everything().

For difference, yes, it matters what the first object is, and your example
does give a case where things change.  But it's not an example that seems
like it has a use case.

Agreed.  It only has a confusion case, where somebody has put an echo() as the first child to a difference.


I'm left wondering if it really matters whether if(false) and empty for return an empty set or a NULL, since most of the time it will give the same result anyway.

Intersection is the big one.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: if(false) returns empty object in RC3, breaks code from 2019.05

adrianv
In RC4, if (false) doesn't create a group in the tree, so intersection works
the way it did before.  


JordanBrown wrote

> [ Sigh.  Not my day.  First I sent this from the wrong address, then
> when I went to resend it from the right address I resent the one of
> Adrian's messages instead of this one. ]
>
> On 12/26/2020 6:58 PM, adrianv wrote:
>> JordanBrown wrote
>>>> It seems like the truth is that passing assert or echo as children is
>>>> just weird and confusing.
>>> Somewhat.  But they obey the general rule:  they run when you say
>>> children(i) and refer to them.
>> Yes, everything makes sense if you understand the technical details.  But
>> for a casual user it's going to be surprising and puzzling, I think.
>
> Yes.
>
> What I'm saying is that if it's going to be surprising and puzzling, at
> least there should be a simple rule.
>
> (And I think that all variations that have gotten past the first level
> of discussion have that simple rule, that all module-like invocations
> yield children.)
>
>> I think in my own work I'd be more inclined to stick a cube() or
>> something in as a placeholder than text. Or just a comment.
>
> A comment won't be enough if you're leaving a placeholder for child #3 of
> 6.
>
>>>> which already exists, like it or not.
>>> Well, in the RC, which is 95% existing but not 100%.  (I think my
>>> primary contribution to this discussion might be in giving it a name.)
>> What is the missing 5%?  
>
> I wasn't saying that the functionality wasn't in the RC... but rather
> that the RC is an RC, not a release, so doesn't 100% exist.  Behavior in
> a non-release can change; behavior in a release should normally remain
> constant.
>
> (But:  the RC has NULL functionality for echo, assert, intersection, and
> difference, but not for if, for, and modules.  Hans has a PR for if.)
>
>> [ if(true)echo() and if(false)echo() are different from echo() ]
>> Wouldn't these things be "fixed" by having if statements not create
>> geometry?  
>
> Well, by having failing if not generate geometry - having it generate
> NULL - and by having successful if (or else) yield whatever its contents
> yield (which might or might not be NULL).
>
>> I'm in agreement that intersection with empty set should give the
>> empty set.  The problem is that there's no way to specify the
>> complement of the empty set. So I can't do
>> intersection(){
>>    if (condition) thing(); else everything();
>>    otherthing();
>> }
>
> NULL would address this particular need, because a failing if would
> yield NULL, and intersection would be defined to ignore NULL.  So for
> intersection purposes, NULL would be sort of like everything().
>
>> For difference, yes, it matters what the first object is, and your
>> example
>> does give a case where things change.  But it's not an example that seems
>> like it has a use case.
>
> Agreed.  It only has a confusion case, where somebody has put an echo()
> as the first child to a difference.
>
>
>> I'm left wondering if it really matters whether if(false) and empty
>> for return an empty set or a NULL, since most of the time it will give
>> the same result anyway.
>
> Intersection is the big one.
>
>
> _______________________________________________
> OpenSCAD mailing list

> Discuss@.openscad

> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org





--
Sent from: http://forum.openscad.org/

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
12