Skip to content

function variable binding is not consistent with openscad #29

Open
tolomea opened this Issue Mar 3, 2012 · 8 comments

2 participants

@tolomea
tolomea commented Mar 3, 2012

In particular it looks like openscad is binding free variables at module invocation and implicitcad at module definition.

This scad:

function bob(x) = x*y;
y=10;
sphere(bob(1));

produces a sphere of size 10 in openscad and the following error in implicitcad:

Module sphere failed with the following messages:
error in computing value for arugment r: Can't multiply objects of types Number and Undefined.
Nothing to render

This scad:

y=10;
function bob(x) = x*y;
y=1;
sphere(bob(1));

produces a sphere of size 1 in openscad and a sphere of size 10 in implicitcad.

I'm not sure how useful this behavior is, but if we were not going to support it we would need to come up with a good error message that catches both cases.

@colah
Owner
colah commented Mar 5, 2012

I'm going to say this is clearly a bug in OpenSCAD. It's just a consequence of them not properly handling variables.

@colah colah closed this Mar 5, 2012
@colah colah reopened this Mar 5, 2012
@colah
Owner
colah commented Mar 5, 2012

Reopened per request for further discussion.

@tolomea
tolomea commented Mar 5, 2012

I'm not sure I agree. Certainly what they do isn't unprecedented, I know several languages that bind like that. This snippet of Python for example:

bob=lambda x:x*y
y=10
print bob(1)

It's also important to note that the example I gave is trivial and makes the OpenSCAD approach look silly, in more complex examples it's not as clear cut.
Additionally I'd expect a non trivial portion of the models out there depend on this (even if not intentionally) and I'm wary of constructing barriers to adoption.

I found this from working through the OpenSCAD examples, here's example number 1:

module example001()
{
function r_from_dia(d) = d / 2;

module rotcy(rot, r, h) {
    rotate(90, rot)
        cylinder(r = r, h = h, center = true);
}

difference() {
    sphere(r = r_from_dia(size));
    rotcy([0, 0, 0], cy_r, cy_h);
    rotcy([1, 0, 0], cy_r, cy_h);
    rotcy([0, 1, 0], cy_r, cy_h);
}

size = 50;
hole = 25;

cy_r = r_from_dia(hole);
cy_h = r_from_dia(size * 2.5);

}

example001();

@colah
Owner
colah commented Mar 5, 2012

I'll address OpenSCAD first. While its true that this introduces inconsistencies, it only does when the code is written in a strange manner. All variables in OpenSCAD are effectively constant, so it is very strange to not just declare them at the top. In fact, examples where this isn't done seem rather forced and can be trivially rewritten in another way.

The real issue is whether this is good or bad behaviour. Since you brought up Python, I'll use it to illustrate why I think the "use execution scope rather than declaration scope" approach is a poor choice. Please forgive me if this becomes a rant -- I've had a number of bugs in Python related to this sort of thing.

b=1
a=3+b
b=2
print a

Of course, a is 3+1=4. But what if a had been a function?

b=1
a= lambda x : 3+b
b=2
print a(0)

But now the result is 3+2. How does that make any sense?!? This is a very strange thing. Essentially, we are lazily interpreting functions, but not other values. And while laziness works fine in a functional language like Haskell, it leads to very strange results in an imperative language, where results depend on a state that has since changed.

We have arguments for when we want to pass values to the function for execution. I spent a good hour making sure functions could have as many arguments as one wanted. Let's leave scope for passing things to a function at declaration.

There are some times when special treatment of functions is nice -- like making recursion easy! -- but this seems silly.

@tolomea
tolomea commented Mar 5, 2012

All variables in OpenSCAD are effectively constant.

function bob(x)=x*y;
y=10;
echo(bob(1));
y=20;
echo(bob(1));

You are correct about that. I had misunderstood, I hadn't thought to try changing the value of a variable. That makes this a different type of beastie yet again.

so it is very strange to not just declare them at the top.

Actually it becomes irrelevant where they are declared. And copying OpenSCAD's behavior should be relatively easy. Alternatively if we wish to break from OpenSCAD's behavior we have the ability to produce comprehensive warnings about the matter.

Let's leave scope for passing things to a function at declaration.

If we were designing the language I would agree, but one of our main goals has to be ease of transition. The best option for ease of transition is conformant behavior, the fall back option is really through warnings so that updating code for our variant of the language is a mechanical process.

Essentially, we are lazily interpreting functions, but not other values.

This is off topic but: Not at all, it's clearly defined in imperative languages that the statements in a function are evaluated when the function is invoked. A function invocation in an imperative language is "go do that stuff now".

@tolomea
tolomea commented Mar 5, 2012

Actually it becomes irrelevant where they are declared.

That is because in a system where variables can't be modified we are free to move them relative to the other content. Alternatively you could say that by definition variable sets are done first regardless of where they appear.

@colah
Owner
colah commented Mar 7, 2012

Actually it becomes irrelevant where they are declared.

Well, except for things like readability.

And copying OpenSCAD's behavior should be relatively easy.

Mimicking OpenSCAD's behaviour would turn us into a preprocessor masquerading as a language. We would cease to be Turing complete.

Also, I've had at least three emails from people who are ecstatic about just this.

Alternatively if we wish to break from OpenSCAD's behavior we have the ability to produce comprehensive warnings about the matter.

Go on?

If we were designing the language I would agree, but one of our main goals has to be ease of transition.

One of them, but not at the cost of sacrificing real programming capacity.

A function invocation in an imperative language is "go do that stuff now".

But what that stuff is is decided at function execution, not declaration. Again, consider my numeric vs function example.

...

In any case, I spent some time asking people what the behaviour should be, to sanity check myself. The people I asked were fellow hacklab.toers or visiters and all had extensive programming experience. About half the people went one way, half the other (slightly more for declaration scope over execution scope). One person I spoke to was a language design PhD student and we talked extensively about it. He favoured the declaration scope behaviour.

In any case, we can address this more at a later date if you want. For now, I really need to focus on the rendering engine.

@tolomea
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.