I created a PR for this on Github and was asked to solicit feedback on
the mailing list. Link to the PR:
https://github.com/openscad/openscad/pull/5383
Many languages have a similar operator, including C#, PHP, Perl,
Objective-C, and Javascript. Most use "??" although ObjC uses "?:" and
Perl uses "//". See
https://en.wikipedia.org/wiki/Null_coalescing_operator
It is defined as follows:
x ?? y
evaluates x
once, and if it is not undef
, the result of
the expression is the value of x
and y
is not evaluated. If x
is
undef
, then y
is evaluated, and the result is the value of y
.
If x
is a single variable lookup, not a compound expression, then
that lookup is performed without causing a warning if the lookup fails
is_undef
function.The operator's precedence is higher than the ternary operator x ? y : z
but lower than the logical operators ||
and &&
. It is also
right-associative, so x ?? y ?? z
parses as x ?? (y ?? z)
. This is
the same as C#, PHP, and Javascript, although Javascript treats ??
and '||with the same precedence. I believe right-associativity is proper (even though the result would be the same either way) since the warning suppression wouldn't apply to
yif it were parsed as
(x ??
y) ?? z`.
Sources for operator precedence and associativity:
C#: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/
PHP: https://www.php.net/manual/en/language.operators.precedence.php
Javascript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence
Rationale:
The reason I wanted to add this operator, as I stated in the PR, is
because I wanted to add a compact way to provide a default for
variables which are possibly undefined at the point they are
referenced. Currently, the only way to do this is to use a ternary
construct with is_undef: !is_undef(var) ? var : default
. This has
some drawbacks: The variable must be written out twice, and is looked
up twice, once by is_undef
and again when the conditional evaluates
its ifexpr
. I could define function default(x, y) = !is_undef(x) ? x : y
, but in this case x
and y
are always evaluated before
calling the function, and if the variable lookup x
fails, then it
causes a warning, and I set the option "Stop on the first warning" so
I don't get a cascade of warnings which I have to scroll through.
Other potential solutions:
default(x, y, ...)
which takes any number
undef
. This isn't as compact as adding an
Context::lookup_variable
return
Value::undef(...)
, although it can't return it as a reference, it
std::unique_ptr<Value>
or something
I don't foresee any potential conflicts with adding this operator. The
only other syntax that uses "?" is the ternary operator, and "??" is
currently invalid syntax since there is no unary "?" operator.
Thoughts? Adding it as a built-in might be simpler, but I prefer the
compactness of the operator.
Remember that "undefined variable" means a variable that has never been
assigned to. A variable that has been set equal to undef is a defined
variable with the special undefined value.
I don't have a strong opinion on the feature, except for the magic
treatment of undefined variables. It's terser than default(a, b), but
it's also yet another piece of syntax that a programmer must memorize.
The magic treatment of undefined variables gives me pause. One of the
critically important features of language design is that it be
consistent. Here that means that uttering a particular name should do
the same thing in every expression context. We already have a violation
of that rule: is_undef() is magic and suppresses undefined-variable
errors. If you utter an undefined variable name in any other expression
context, you get an error; when you give one to this specific function
you don't.
Nobody will die, but every such magic case that you add makes the
language harder to understand.
Let's look at other languages for a moment. (Again, "not declared or
initialized" is different from "initialized to the 'undefined" value".)
Perl... doesn't report undefined variables at all.
JavaScript... undefined variables are an error, even in ??.
PHP... ?? behaves as you describe.
C#... gives an error on undefined names, even with ??.
Objective-C... gives an error on undefined names, even with ?:.
Python... you don't mention it, but the "equivalent" is-None test gives
an error on undefined variables.
So while many languages have "if this variable exists and has a
non-null-ish value, use it, else use this other value" construct, few
will let it suppress an undefined variable error. The only one that
checks for undefined variables at all and has this construct is PHP.
Turning undefined names into undef is in my opinion a total
non-starter. It means that a typo is not detected immediately, but may
yield cascading errors much later and farther away from the error.
There's a reason that almost every large-scale programming language
gives errors on undefined variables.
Do we need an easy way to detect and supply defaults for undefined
variables in OpenSCAD?
Well... maybe.
Inside one program, mostly I would say "no". You control the horizontal
and the vertical. https://www.youtube.com/watch?v=8CtjhWhw2I8 Ensure
that stuff is initialized before you use it.
In a library the picture is different. The library is separate from its
caller and doesn't want to tell the caller to initialize defaults.
In an "include"-style library, there is already something of an answer.
Inclusion has a magic behavior (and again, magic is bad) that it's OK
for the main program to redefine a variable defined in the included
file. Have the library set any global defaults that it wants, and then
have the caller override them if it wants to. This works for both
lexical-scope and call-scope ($) variables.
In a "use"-style library, the caller's lexical-scope variables are not
accessible to the library, and vice versa, so for lexical-scope
variables there's no opportunity to need such a feature. However, the
feature might be applicable to call-scope ($) variables.
Net..
Absent the undefined-variable magic, shrug. I wouldn't add it, but I
also wouldn't object.
With the undefined-variable magic, I don't like it. I won't stand in
front of a train to stop it, though. We might need something better
than we have, in particular for $ variables being passed to libraries,
but I'm not convinced that this is the right answer there.
While one can easily make a default(x,dflt)
function, which allows a compound initial value which evaluates exactly once, the alternate default value is forced to be evaluated even if the initial value was not undef. The proposed ??
operator fixes that issue.
The part of me that has written way too many thousand lines of bash code (which should probably be ignored) is screaming for a syntax using &&
or ||
. It should be doable, if you were to change &&
from returning a boolean, to returning the first value if it evaluates as false, and the second value otherwise. For ||
return the first value if it evaluates as true, or the second value otherwise. This is partly based on the fact that undef
evaluates as false. I’m fairly sure, though, that there’s a usage flaw there, if you actually want to return a boolean when an undef indicates failure.
-Revar
On Oct 19, 2024, at 5:38 PM, Jordan Brown via Discuss <discuss@lists.openscad.org> wrote:
Remember that "undefined variable" means a variable that has never been assigned to. A variable that has been set equal to undef is a defined variable with the special undefined value.
I don't have a strong opinion on the feature, except for the magic treatment of undefined variables. It's terser than default(a, b), but it's also yet another piece of syntax that a programmer must memorize.
The magic treatment of undefined variables gives me pause. One of the critically important features of language design is that it be consistent. Here that means that uttering a particular name should do the same thing in every expression context. We already have a violation of that rule: is_undef() is magic and suppresses undefined-variable errors. If you utter an undefined variable name in any other expression context, you get an error; when you give one to this specific function you don't.
Nobody will die, but every such magic case that you add makes the language harder to understand.
Let's look at other languages for a moment. (Again, "not declared or initialized" is different from "initialized to the 'undefined" value".)
Perl... doesn't report undefined variables at all.
JavaScript... undefined variables are an error, even in ??.
PHP... ?? behaves as you describe.
C#... gives an error on undefined names, even with ??.
Objective-C... gives an error on undefined names, even with ?:.
Python... you don't mention it, but the "equivalent" is-None test gives an error on undefined variables.
So while many languages have "if this variable exists and has a non-null-ish value, use it, else use this other value" construct, few will let it suppress an undefined variable error. The only one that checks for undefined variables at all and has this construct is PHP.
Turning undefined names into undef is in my opinion a total non-starter. It means that a typo is not detected immediately, but may yield cascading errors much later and farther away from the error. There's a reason that almost every large-scale programming language gives errors on undefined variables.
Do we need an easy way to detect and supply defaults for undefined variables in OpenSCAD?
Well... maybe.
Inside one program, mostly I would say "no". You control the horizontal and the vertical. Ensure that stuff is initialized before you use it.
In a library the picture is different. The library is separate from its caller and doesn't want to tell the caller to initialize defaults.
In an "include"-style library, there is already something of an answer. Inclusion has a magic behavior (and again, magic is bad) that it's OK for the main program to redefine a variable defined in the included file. Have the library set any global defaults that it wants, and then have the caller override them if it wants to. This works for both lexical-scope and call-scope ($) variables.
In a "use"-style library, the caller's lexical-scope variables are not accessible to the library, and vice versa, so for lexical-scope variables there's no opportunity to need such a feature. However, the feature might be applicable to call-scope ($) variables.
Net..
Absent the undefined-variable magic, shrug. I wouldn't add it, but I also wouldn't object.
With the undefined-variable magic, I don't like it. I won't stand in front of a train to stop it, though. We might need something better than we have, in particular for $ variables being passed to libraries, but I'm not convinced that this is the right answer there.
_______________________________________________
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
I definitely agree that the magic of suppressing undefined errors with
is_undef is kind of a hack, but there should be some way to suppress
the warning. In Python you can do a try:
and except:
, in
JavaScript, it's try
/ catch
, same in C# if you have a dynamic
variable that you access ann undefined property on.
Implementing a full try / catch or warning suppression is probably not
easy and I'm not sure what value it would provide other than this one
specific case.
Your point about lexical variables is valid. Would changing it to only
suppress warnings for $
scoped variables be less of a hack, or would
it just be adding a special case on top of a special case?
On Sat, Oct 19, 2024 at 7:37 PM Jordan Brown
openscad@jordan.maileater.net wrote:
Remember that "undefined variable" means a variable that has never been assigned to. A variable that has been set equal to undef is a defined variable with the special undefined value.
I don't have a strong opinion on the feature, except for the magic treatment of undefined variables. It's terser than default(a, b), but it's also yet another piece of syntax that a programmer must memorize.
The magic treatment of undefined variables gives me pause. One of the critically important features of language design is that it be consistent. Here that means that uttering a particular name should do the same thing in every expression context. We already have a violation of that rule: is_undef() is magic and suppresses undefined-variable errors. If you utter an undefined variable name in any other expression context, you get an error; when you give one to this specific function you don't.
Nobody will die, but every such magic case that you add makes the language harder to understand.
Let's look at other languages for a moment. (Again, "not declared or initialized" is different from "initialized to the 'undefined" value".)
Perl... doesn't report undefined variables at all.
JavaScript... undefined variables are an error, even in ??.
PHP... ?? behaves as you describe.
C#... gives an error on undefined names, even with ??.
Objective-C... gives an error on undefined names, even with ?:.
Python... you don't mention it, but the "equivalent" is-None test gives an error on undefined variables.
So while many languages have "if this variable exists and has a non-null-ish value, use it, else use this other value" construct, few will let it suppress an undefined variable error. The only one that checks for undefined variables at all and has this construct is PHP.
Turning undefined names into undef is in my opinion a total non-starter. It means that a typo is not detected immediately, but may yield cascading errors much later and farther away from the error. There's a reason that almost every large-scale programming language gives errors on undefined variables.
Do we need an easy way to detect and supply defaults for undefined variables in OpenSCAD?
Well... maybe.
Inside one program, mostly I would say "no". You control the horizontal and the vertical. Ensure that stuff is initialized before you use it.
In a library the picture is different. The library is separate from its caller and doesn't want to tell the caller to initialize defaults.
In an "include"-style library, there is already something of an answer. Inclusion has a magic behavior (and again, magic is bad) that it's OK for the main program to redefine a variable defined in the included file. Have the library set any global defaults that it wants, and then have the caller override them if it wants to. This works for both lexical-scope and call-scope ($) variables.
In a "use"-style library, the caller's lexical-scope variables are not accessible to the library, and vice versa, so for lexical-scope variables there's no opportunity to need such a feature. However, the feature might be applicable to call-scope ($) variables.
Net..
Absent the undefined-variable magic, shrug. I wouldn't add it, but I also wouldn't object.
With the undefined-variable magic, I don't like it. I won't stand in front of a train to stop it, though. We might need something better than we have, in particular for $ variables being passed to libraries, but I'm not convinced that this is the right answer there.
I have a large project split into multiple files. The main.scad defines
various variables to affect the view and they are acted upon in the
individual files but any of those can be opened to show a sub-assembly and
of course the variables are not defined so I have lots of X =
(is_undef($X) ? X : $X) * X_travel / 2; where X is local to the file and
can be set by the customiser when the sub assembly is viewed but $X is
passed by main.scad and overrides it and is set by the customiser when the
main assembly is viewed. Note I don't use the customiser to customise
printed parts. I use it to pose assembly views.
So a ?? operator would be nice in my opinion.
On Sun, 20 Oct 2024 at 09:27, Katie Rust via Discuss <
discuss@lists.openscad.org> wrote:
I definitely agree that the magic of suppressing undefined errors with
is_undef is kind of a hack, but there should be some way to suppress
the warning. In Python you can do a try:
and except:
, in
JavaScript, it's try
/ catch
, same in C# if you have a dynamic
variable that you access ann undefined property on.
Implementing a full try / catch or warning suppression is probably not
easy and I'm not sure what value it would provide other than this one
specific case.
Your point about lexical variables is valid. Would changing it to only
suppress warnings for $
scoped variables be less of a hack, or would
it just be adding a special case on top of a special case?
On Sat, Oct 19, 2024 at 7:37 PM Jordan Brown
openscad@jordan.maileater.net wrote:
Remember that "undefined variable" means a variable that has never been
assigned to. A variable that has been set equal to undef is a defined
variable with the special undefined value.
I don't have a strong opinion on the feature, except for the magic
treatment of undefined variables. It's terser than default(a, b), but it's
also yet another piece of syntax that a programmer must memorize.
The magic treatment of undefined variables gives me pause. One of the
critically important features of language design is that it be consistent.
Here that means that uttering a particular name should do the same thing in
every expression context. We already have a violation of that rule:
is_undef() is magic and suppresses undefined-variable errors. If you utter
an undefined variable name in any other expression context, you get an
error; when you give one to this specific function you don't.
Nobody will die, but every such magic case that you add makes the
language harder to understand.
Let's look at other languages for a moment. (Again, "not declared or
initialized" is different from "initialized to the 'undefined" value".)
Perl... doesn't report undefined variables at all.
JavaScript... undefined variables are an error, even in ??.
PHP... ?? behaves as you describe.
C#... gives an error on undefined names, even with ??.
Objective-C... gives an error on undefined names, even with ?:.
Python... you don't mention it, but the "equivalent" is-None test gives
an error on undefined variables.
So while many languages have "if this variable exists and has a
non-null-ish value, use it, else use this other value" construct, few will
let it suppress an undefined variable error. The only one that checks for
undefined variables at all and has this construct is PHP.
Turning undefined names into undef is in my opinion a total
non-starter. It means that a typo is not detected immediately, but may
yield cascading errors much later and farther away from the error. There's
a reason that almost every large-scale programming language gives errors on
undefined variables.
Do we need an easy way to detect and supply defaults for undefined
variables in OpenSCAD?
Well... maybe.
Inside one program, mostly I would say "no". You control the horizontal
and the vertical. Ensure that stuff is initialized before you use it.
In a library the picture is different. The library is separate from its
caller and doesn't want to tell the caller to initialize defaults.
In an "include"-style library, there is already something of an answer.
Inclusion has a magic behavior (and again, magic is bad) that it's OK for
the main program to redefine a variable defined in the included file. Have
the library set any global defaults that it wants, and then have the caller
override them if it wants to. This works for both lexical-scope and
call-scope ($) variables.
In a "use"-style library, the caller's lexical-scope variables are not
accessible to the library, and vice versa, so for lexical-scope variables
there's no opportunity to need such a feature. However, the feature might
be applicable to call-scope ($) variables.
Net..
Absent the undefined-variable magic, shrug. I wouldn't add it, but I
also wouldn't object.
With the undefined-variable magic, I don't like it. I won't stand in
front of a train to stop it, though. We might need something better than
we have, in particular for $ variables being passed to libraries, but I'm
not convinced that this is the right answer there.
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
Coming from a procedural programming background ( IBM Fortran IV, 60
years ago), I tend to view undefined values as potential sources of
error, so I instinctively prefer assigning values to variables upon
creation. I realize this might reflect my procedural mindset, but it
helps me maintain clarity and predictability in my code.
On 20/10/2024 12:43, nop head via Discuss wrote:
I have a large project split into multiple files. The main.scad
defines various variables to affect the view and they are acted upon
in the individual files but any of those can be opened to show a
sub-assembly and of course the variables are not defined so I have
lots of X = (is_undef($X) ? X : $X) * X_travel / 2; where X is local
to the file and can be set by the customiser when the sub assembly is
viewed but $X is passed by main.scad and overrides it and is set by
the customiser when the main assembly is viewed. Note I don't use the
customiser to customise printed parts. I use it to pose assembly views.
But that isn't possible with my multi-file projects. Each assembly needs to
work alone but also be able to be used in the main application and its
customiser values overridden from there. So I definitely need to test if a
variable from the main application is present and use it, else use the
local value. The $variable from the main file may be present or not.
On Sun, 20 Oct 2024 at 14:30, Raymond West via Discuss <
discuss@lists.openscad.org> wrote:
Coming from a procedural programming background ( IBM Fortran IV, 60
years ago), I tend to view undefined values as potential sources of
error, so I instinctively prefer assigning values to variables upon
creation. I realize this might reflect my procedural mindset, but it
helps me maintain clarity and predictability in my code.
On 20/10/2024 12:43, nop head via Discuss wrote:
I have a large project split into multiple files. The main.scad
defines various variables to affect the view and they are acted upon
in the individual files but any of those can be opened to show a
sub-assembly and of course the variables are not defined so I have
lots of X = (is_undef($X) ? X : $X) * X_travel / 2; where X is local
to the file and can be set by the customiser when the sub assembly is
viewed but $X is passed by main.scad and overrides it and is set by
the customiser when the main assembly is viewed. Note I don't use the
customiser to customise printed parts. I use it to pose assembly views.
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
On 10/19/2024 7:20 PM, Revar Desmera via Discuss wrote:
While one can easily make a default(x,dflt)
function, which allows a
compound initial value which evaluates exactly once, the alternate
default value is forced to be evaluated even if the initial value was
not undef. The proposed ??
operator fixes that issue.
Indeed.
The part of me that has written way too many thousand lines of bash
code (which should probably be ignored) is screaming for a syntax
using &&
or ||
. It should be doable, if you were to change &&
from returning a boolean, to returning the first value if it evaluates
as false, and the second value otherwise. For ||
return the first
value if it evaluates as true, or the second value otherwise. This is
partly based on the fact that undef
evaluates as false. I’m fairly
sure, though, that there’s a usage flaw there, if you actually want to
return a boolean when an undef indicates failure.
That's how JavaScript && and || are defined. It's a very useful
definition for ||; less so for &&. (Less so for && because it returns
the first falsey value, and there aren't very many different falsey
values.) It was my first thought.
There are three problems with just adopting that:
undef was not available in fortran. We used to use -99 quite a lot 🙂,
but then of course variable names could be re-used, which solved many
problems (and created a few, since variable names were restricted to 4
characters in length. I still use i to n for loop counters - usually j
or k, maybe i or n, but never l or m for some reason).
On 20/10/2024 14:37, nop head via Discuss wrote:
But that isn't possible with my multi-file projects. Each assembly
needs to work alone but also be able to be used in the main
application and its customiser values overridden from there. So I
definitely need to test if a variable from the main application is
present and use it, else use the local value. The $variable from the
main file may be present or not.
On Sun, 20 Oct 2024 at 14:30, Raymond West via Discuss
discuss@lists.openscad.org wrote:
Coming from a procedural programming background ( IBM Fortran IV, 60
years ago), I tend to view undefined values as potential sources of
error, so I instinctively prefer assigning values to variables upon
creation. I realize this might reflect my procedural mindset, but it
helps me maintain clarity and predictability in my code.
On 20/10/2024 12:43, nop head via Discuss wrote:
I have a large project split into multiple files. The main.scad
defines various variables to affect the view and they are acted
upon
in the individual files but any of those can be opened to show a
sub-assembly and of course the variables are not defined so I have
lots of X = (is_undef($X) ? X : $X) * X_travel / 2; where X is
local
to the file and can be set by the customiser when the sub
assembly is
viewed but $X is passed by main.scad and overrides it and is set by
the customiser when the main assembly is viewed. Note I don't
use the
customiser to customise printed parts. I use it to pose assembly
views.
_______________________________________________
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
"undef was not available in fortran... since variable names were restricted
to 4 characters in length..."
That brings back some memories... One of my favorites is that the
following is, or at least was, a valid program in Fortran 66 and 77:
Reality, Sanity, and Me
STOP
END
I do the same with i, j, k, and l. But the underlying issue here is that
variables cannot be updated once set in a given context. This has always
thrown me in OpenSCAD.
On Sun, Oct 20, 2024 at 1:59 PM Raymond West via Discuss <
discuss@lists.openscad.org> wrote:
undef was not available in fortran. We used to use -99 quite a lot 🙂,
but then of course variable names could be re-used, which solved many
problems (and created a few, since variable names were restricted to 4
characters in length. I still use i to n for loop counters - usually j or
k, maybe i or n, but never l or m for some reason).
On 20/10/2024 14:37, nop head via Discuss wrote:
But that isn't possible with my multi-file projects. Each assembly needs
to work alone but also be able to be used in the main application and its
customiser values overridden from there. So I definitely need to test if a
variable from the main application is present and use it, else use the
local value. The $variable from the main file may be present or not.
On Sun, 20 Oct 2024 at 14:30, Raymond West via Discuss <
discuss@lists.openscad.org> wrote:
Coming from a procedural programming background ( IBM Fortran IV, 60
years ago), I tend to view undefined values as potential sources of
error, so I instinctively prefer assigning values to variables upon
creation. I realize this might reflect my procedural mindset, but it
helps me maintain clarity and predictability in my code.
On 20/10/2024 12:43, nop head via Discuss wrote:
I have a large project split into multiple files. The main.scad
defines various variables to affect the view and they are acted upon
in the individual files but any of those can be opened to show a
sub-assembly and of course the variables are not defined so I have
lots of X = (is_undef($X) ? X : $X) * X_travel / 2; where X is local
to the file and can be set by the customiser when the sub assembly is
viewed but $X is passed by main.scad and overrides it and is set by
the customiser when the main assembly is viewed. Note I don't use the
customiser to customise printed parts. I use it to pose assembly views.
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