Closed ephetic closed 7 months ago
I like the idea. May need to let things settle in my mind before I can implement it however this does solve the public/private variable concern that I have had.
The problem I see with it is that it would also be nice to have local function declarations (which ought to be consistent the ./file()
interface), but this local scope syntax is close to what a local function declaration might be and which might be more important to work out. What do you think of this:
var offset = 3;
var vase = {
var size = 3;
var offset_xy = cube.x/2 + offset; // NB `offset` could be closure of outer scope or module global
var offset_z = cube.z + offset;
cylinder(radius=size, height=size)
->shape translate(x=offset_xy, y=offset_xy, z=offset_z)
};
var my_vase = .vase(size=5); // leading `.` to mirror `./file()` usage.
var iife_slab = {
var size = 3;
var thickness = 1;
cube(x=size, y=2*size, z=thickness)
}(); // NB immediately invoked, so `iife_slab` is a shape, not a function.
Alternately, could use fn
instead of var
to differentiate, the latter being equivalent to the IIFE form.
when I write var c = cube();
, what actually is c
? Geometry? A function to create geometry?
Tbh I wonder if it would be better to use something like deno core and provide bindings for an existing language like Typescript or LUA.
@benthillerkus that depends. In this case cube()
produces a raw shape which is a native binding. However ./file()
would produce what I call internally a script instance. Its closer to an object in the scripting language sense. They should feel about the same but have some differences type wise.
As for binding there are existing projects doing that approach. You could check out https://ocjs.org/ if that is your preferred way of developing. I wanted the flexibility of having a domain specific language (the DSL of DSLCAD).
Per #18, how would blocks work with the pipe operator?
As an (immediately invoked function) expression, would you want to do this? If the block/lambda has access to the outer scope, you could just reference it rather than pass it as an argument, further confusing the difference between a block and lambda.
var c = cube() ->shape {
var shape;
scale(shape=shape, scale=2);
};
As a function definition, the vars are parameters as with files, so there'd be nothing different about the definition in the previous comment. I can't think of a good reason for using the pipe operator as a weird single argument capture operator in the definition itself, like this:
fn rc = cube() ->shape {
var shape;
var scale = 2;
scale(shape=shape, scale=scale);
};
// now what? is shape no longer bindable?
rand()
->scale rc();
The way I see it right now we need two syntaxes. First an expression syntax as mentioned above for nested scopes:
var c = cube() -> shape {
...
}
Then the statement syntax which would have some more limitations:
fn rc {
var scale = 2;
cube() ->shape scale(scale=scale);
}
Note how the fn syntax essentially must go directly into a nested scope. That makes it possible to set it up to always be callable.
I think the expression syntax will come first because I don't see any more issues with its implementation. For the statement syntax (function declaration if you want to call it that) there are some open questions in my head:
fn rc | scale, angle | {
cube() ->shape scale(scale=scale) ->shape rotate(angle=angle);
}
// constants.ds
var scale = 10;
var origin = [0,0,0];
...
// main.ds
var scale = ./constants.scale;
If so, then the statement usage would mirror it. The expressions could also mirror it, giving rise to maps:
// constants.ds
var scale = 10;
var origin = {
var x = 0;
var y = 0;
var z = 0;
}
var base = { cube(); }
...
// main.ds
var c = ./constants.base() ->shape translate(x=./constants.origin.x);
It's not an elegant map syntax, but it's nice to have and nice to keep usage consistent between files, expressions, and statements. I'd even go so far as to make {}
totally equivalent to inlined-files, requiring ()
to evaluate like a function and .
to access module properties (including nested-modules), default
argument if you so chose, etc. The only downside I see to this is that I don't get the traditional nested scope like { x = 1; { y = 2; { z = x + y; }}}
but I think that's a small price to pay for a simple and consistent mental model. Like the refactoring story would require no semantic changes:
// simple value
var z = 10;
// computed value
var z = {
var r = pi() ^ 2;
cos(radians=r);
}();
// computed value from outer scope
var z = {
var r = pi() ^ 2;
cos(radians=r);
}(r=pi() ^ 3);
// reused computed value
var zfn = {
var r = pi() ^ 2;
cos(radians=r);
};
var z1 = .zfn();
var z2 = .zfn(r=pi()^3);
// eventually moved into separate file zfn.ds
var z1 = ./zfn();
var z2 = ./zfn(r=pi()^3);
TBH I am starting to think we are losing the point here. Lets bring this back to basics. From the original post I see a clear need:
I am comfortable with scope nesting to try to solve this.
Given that we don't want clojure's what does this give you that using a separate file does not? Now I mean really from a "I want to build 3D parts" point of view. Not a "lets design a cool programming language" point of view.
Solving that problem would be a non-intuitive side effect of all of this that a private
keyword would solve much more clearly.
The original ask and subsequent discussion was about tools for keeping code well organized without introducing too many new things to the language (e.g. local file definitions rather than full-fledged functions). This is one of the areas I think OpenSCAD is deficient, whereas host languages are too heavy a solution to.
But they are for sure heavily dependent on my language preferences. I don't like large scopes. I don't like a preponderance of small files. Both consume mental resources I need for working on the problem at hand, at least for me.
Hey. Im happy to announce that scopes are finally available.
https://dslcad.com/concepts/#scopes
You can use them in the web editor or custom builds. They will be made available in the pre-built version of the 0.0.4
release.
First class functions will probably come soon as a side effect of this work.
e.g.