Function with named arguments and default values

14 views
Skip to first unread message

Wink Saville

unread,
Jun 14, 2020, 6:46:25 PM6/14/20
to Curv
So this works:

let
  tri c = polygon [[-0.5,0], [0,1], [0.5, 0]]
     >> colour c
in
  tri red


I see a red triangle, but I'd like to provide a default for the color c and then invoke `tri` with no parameters. According to Records page where it says:

"Record comprehensions permit you override fields in existing records, or specify default values, using the ... operator. You can add a field to a record conditionally, using an if statement, and you can even generate a set of fields using a loop."

So I was hoping something like this would work:

let
  tri {c: red} = polygon [[-0.5,0], [0,1], [0.5, 0]]
     >> colour c
in
  tri


But I get an error:

ERROR: c: not defined
at file "polygons.curv":
3|      >> colour c
                  ^

So I'm obviously using the wrong syntax and or I've misunderstood something.

Doug Moen

unread,
Jun 14, 2020, 10:42:31 PM6/14/20
to Curv
If you write

let
  tri {c = red} = polygon [[-0.5,0], [0,1], [0.5, 0]]
     >> colour c
in
  row[tri {}, tri {c: black}]

then `tri` is a function, and it must be called with an argument. Note that the default value of `c` is given after an `=`. The argument {} is an empty record, which then relies on the fact that `c` has a default value of `red`.

If you want to be able to invoke `tri` with no parameter, then `tri` must be simultaneously a shape and a function. You can do that by defining `tri` as a "parametric shape", like this:

let
  tri = parametric c = red in
    polygon [[-0.5,0], [0,1], [0.5, 0]]
      >> colour c;
in
  row[tri, tri {c: black}]

This last example isn't documented. The docs/language/Parametric_Shapes.rst needs to be expanded and rewritten.

There is also an older, low level way of defining `tri` as both a shape and a function. You can do this:

let
  tri =
    let
      _tri {c=red} = polygon [[-0.5,0], [0,1], [0.5, 0]] >> colour c;
    in {
      call: _tri;
      ... _tri {};
    };
in
  row[tri, tri {c: black}]

It's possible to figure this out from the documentation, but you would be further ahead by reading the source code for `cube`, `box` and other similar shape primitives, in the standard library: lib/curv/std.curv

Wink Saville

unread,
Jun 15, 2020, 2:10:02 PM6/15/20
to Doug Moen, Curv

This works great:

let
  tri = parametric c = red in
    polygon [[-0.5,0], [0,1], [0.5, 0]]
      >> colour c;
in
  row[tri, tri {c: black}]

But now I expect the following code to work, but it doesn't. Well, actually the
code is "legal" (no compile errors), but when you run code no picker
is shown:

let
  tri = parametric c :: colour_picker = red in
    polygon [[-0.5,0], [0,1], [0.5, 0]]
      >> colour c;
in
  row[tri, tri {c: black}]


So your first suggestion, using `{c = red}`, clearly and succinctly defines the
programmers intent and the following code should show 3 triangles;
two red and one black:  

let
  tri {c = red} = polygon [[-0.5,0], [0,1], [0.5, 0]]
     >> colour c
in
  row[tri, tri {}, tri {c: black}]

Of course, if your desire is to actually support pickers using parametric in
a function definition, that's a fine feature for the future. And if you allow
the above code to work then `row[tri, tri {}, tri {c: black]` still works with both
syntaxes.

So, please don't require an empty record parameter if all the fields have
default values.

Doug Moen

unread,
Jun 15, 2020, 4:49:01 PM6/15/20
to Curv
On Mon, Jun 15, 2020, at 2:09 PM, Wink Saville wrote:
when you run code no picker is shown:

let
  tri = parametric c :: colour_picker = red in
    polygon [[-0.5,0], [0,1], [0.5, 0]]
      >> colour c;
in
  row[tri, tri {c: black}]

That is correct. When you pass a parametric shape as an argument to another shape constructor (in this case, 'row'), then the parameter information within the parametric shape is lost. So that means, if you want a picker to be displayed by this program, you need to use the 'parametric' keyword again to make it happen. Like this:

parametric
  tc :: colour_picker = green;
in
  let
    tri = parametric c :: colour_picker = red in
      polygon [[-0.5,0], [0,1], [0.5, 0]] >> colour c;
  in
    row[tri, tri {c:tc}]

And when you do this, you have to decide which instance or instances of 'tri' are parameterized by pickers.

Automatically preserving the shape parameter information of shape constructor arguments is quite complicated, both the internal implementation complexity, and the extra complexity that would be exposed in the language and in the user interface. In this particular example, there would be two colour pickers shown, one for each instance of 'tri', and the GUI would need to make clear which is which. The number of pickers could explode if you have a large program that calls lots of shape constructors.

So this is a big change, and it's not something I plan to move quickly on. Other work needs to be done first.

So your first suggestion, using `{c = red}`, clearly and succinctly defines the
programmers intent and the following code should show 3 triangles;
two red and one black:  

let
  tri {c = red} = polygon [[-0.5,0], [0,1], [0.5, 0]]
     >> colour c
in
  row[tri, tri {}, tri {c: black}]

Of course, if your desire is to actually support pickers using parametric in
a function definition, that's a fine feature for the future. And if you allow
the above code to work then `row[tri, tri {}, tri {c: black]` still works with both
syntaxes.

So, please don't require an empty record parameter if all the fields have
default values.

This program produces an error because in 'row[tri, tri{}, tri{c:black}]', the first list element 'tri' is a function, not a shape. [The error message just needs to be improved.] Curv is a functional language; it is perfectly legal to pass functions around as arguments, so I can't just implicitly turn the expression 'tri' into a function call.

As you have seen, it is possible to construct a value that is simultaneously a shape and a function, but this requires a specialized mechanism ('parametric'), not just an ordinary function definition.

In Curv, you can write 'cube', and get a cube with default dimensions, or you can write 'cube 10' and get a cube of size 10. The object 'cube' is simultaneously a shape and a function. In OpenSCAD, you have to explicitly write 'cube()' or 'cube(10)', you cannot leave out the argument list. This feature of Curv comes at the cost of some additional complexity, and may cause confusion, because most shapes do not have this dual nature of being shapes/functions, and ordinary function definitions do not create shape/function objects. It's a feature I'd consider leaving out, for simplicity, if I were to redesign Curv from scratch.

If I left the feature out, then 'cube' would be a function that is always passed a record argument. You would write 'cube{}' to get the default size cube, and 'cube{d:10}' to get a cube of  size 10. New users would be required to understand the strict difference between a function and a shape early on, and that might eliminate a source of confusion.

Wink Saville

unread,
Jun 15, 2020, 7:09:25 PM6/15/20
to Doug Moen, Curv

In Curv, you can write 'cube', and get a cube with default dimensions, or you can write 'cube 10' and get a cube of size 10. The object 'cube' is simultaneously a shape and a function. In OpenSCAD, you have to explicitly write 'cube()' or 'cube(10)', you cannot leave out the argument list. This feature of Curv comes at the cost of some additional complexity, and may cause confusion, because most shapes do not have this dual nature of being shapes/functions, and ordinary function definitions do not create shape/function objects. It's a feature I'd consider leaving out, for simplicity, if I were to redesign Curv from scratch.

This is your baby and I think you've done a great job,
but a redesign at a stage pre-1.0 is the time to do it :)

I won't discourage you and would help where I could.


If I left the feature out, then 'cube' would be a function that is always passed a record argument. You would write 'cube{}' to get the default size cube, and 'cube{d:10}' to get a cube of  size 10. New users would be required to understand the strict difference between a function and a shape early on, and that might eliminate a source of confusion.

You've convinced me the right approach is to require `{}`. 
 
Reply all
Reply to author
Forward
0 new messages