Open countvajhula opened 4 years ago
This seems like it could have some overlap with pattern matching. Would being able to unpack arguments
instances with pattern matching be enough?
Do you mean something like this?
(define/arguments (my-function args)
(match args
[(arg1 arg2 #:key [key null] . remaining-args)
;; ... body ...)]))
It could be that if such pattern matching existed, we could use it to trivially expand the define/arguments
and apply/arguments
forms to support the kind of unpacking I'm talking about. But being able to do it without an explicit pattern matching step would be a useful addition on top of this, at least in part because the entire body of the function would often be enclosed in a single match clause. For instance in this example, that containing let expression could be eliminated if we could just unpack the args in the function definition. Also, does define/arguments
support specifying default values (like [key null]
) in the definition? A nice thing about the proposed behavior is that it's a neat superset of define
and apply
, essentially so that define/arguments
and apply/arguments
could be drop-in replacements for the built-in define
and apply
, but with seamless packing and unpacking of extra arguments, making it potentially compelling as a standard.
As a digression on this subject that may nevertheless serve as another example, I've been mulling over writing a "generic constructor" as a generic interface. The idea is that when creating types we usually need two constructors, a unary/n-ary constructor (like cons
) and a nullary one (like null
), and sometimes also a variadic one (like list
). But really we only need to specify one of these to unambiguously indicate what type we're trying to create.
Continuing with this line of thought it might make sense to define a generic interface like this:
(define-generics form
(make form element ...))
[edit: looks like this is technically invalid syntax, but that's not really relevant so leaving as is :) ]
Then, (make (list 1 2) 3)
would construct a list, '(1 2 3)
, while (make #(1 2) 3)
would construct a vector. Likewise (make empty-stream 3)
would construct a stream, and (make empty-arguments 1 2 3)
would construct an arguments structure. In all cases we only need to specify the nullary constructor or provide an existing instance of the desired type, and make
stands in for e.g. cons
and stream-cons
or whatever custom constructor someone might define for a type. This also has the advantage over variadic constructors like list
and stream
that we don't need to know what type is being constructed up front, and it can be determined at runtime (e.g. (make val 3)
).
At this point, this is essentially equivalent to data/collection
's gen:collection
, and using conj
, for example.
But since types in general may entail different structures that won't necessarily fit into cons
/null
style construction, we'd want make
to support any function signature -- left to the discretion of the author of the type -- as long as the first argument remains the instance of the type, since we'd need that to be unambiguous in order for the generic interface to dispatch based on it.
In other words, we'd need the generic interface to look more like this:
(define-generics form
(make form . elements)
... where elements
is a packed arguments
structure. As far as I can tell this isn't possible with the existing define-generics
form, so that could motivate either a possible define-generics/arguments
form, or, maybe even make a case for core-level support for arbitrary packed arguments (e.g. python). While generic interfaces are outside the scope of the present issue, this is a situation where in principle we'd need to / like to be able to syntactically unpack at least one argument for dispatch purposes.
Did I mention I'm a huge fan of the arguments package? There's just one thing that I wish it had -- syntactic unpacking of arguments at definition and at invocation time. I've thought about implementing this and submitting a PR when my macro-fu was up to the task, but I'm not there yet and I'm not sure when I'd get to it, so I'm creating this issue here for now as a feature request.
The idea is to be able to identify specific arguments in addition to being able to "pack" the rest, similar to how Python does it.
Spec:
These would essentially just be more general versions of the built-in
apply
anddefine
, with the only difference being that they support anarguments
tail.Example:
Here, the first two arguments to the function should be bound in the body, and if a keyword argument
key
is supplied, it should be bound in the body as well (in this example it should always be bound because of the defaultnull
value). Any other positional or keyword arguments should be packed into theargs
arguments structure, the same as current behavior.Likewise,
... should bind
arg1
to5
andarg2
to7
, and pack(list 8 9)
and(hash '#:kw1 "blue" '#:a 1 '#:b 2)
into theargs
struct.For reference, here is a case where this feature would help, by avoiding the need to explicitly unpack the argument in the body of the function when it is already known to be needed at definition time. There are other such examples in that file, too. And in general, this is verbatim the behavior in python, so python examples of argument packing/unpacking would illustrate this feature as well.