stan icon indicating copy to clipboard operation
stan copied to clipboard

Feature request: variable-length argument lists to user-defined functions

Open ksvanhorn opened this issue 6 years ago • 4 comments

Summary:

Allow user-defined functions to have an argument list that ends with 0 or more arguments all of the same type. Call these optional arguments.

Description:

Currently there is no way to create user-defined functions that have a variable number of arguments. Here are some examples where that can be useful:

  • vector_append(v1, ..., vn): append n vectors of possibly differing lengths.
  • block_diag(M1, ..., Mn): create a block diagonal matrix from n matrices of possibly differing shapes. This kind of thing shows up in state-space models.

Additional Information:

Specifically, I am proposing the following:

  • In the definition of a user-defined function, allow the last argument to have the form T... v for some type T, meaning 0 or more arguments of type T.
  • Within the function body, size(v) refers to the number of optional arguments, and v[i] refers to the i-th optional argument. It is illegal to use v itself in any other way within the function body.

So, for example, if we have a function definition

T f(matrix M, vector... v) {
  // function body
}

and a call f(x, y1, y2), then y1 binds to v[1] and y2 binds to v[2] and size(v) is 2 in the body of f.

There is a simple implementation of this proposal, which I'll describe by example. The above function definition for f is implemented as

T f(matrix M, vector [] v) {
  // function body
}

and the call f(x, y1, y2) is implemented as f(x, {y1, y2}). This implementation does involve the creation of heterogeneous arrays (arrays whose elements may have differing shapes), but such arrays are never passed to any Stan function other than size() and the indexing operator.

ksvanhorn avatar Oct 11 '18 22:10 ksvanhorn

I already know that this implementation works, because I've been using it to simulate variable-length argument lists.

ksvanhorn avatar Oct 11 '18 22:10 ksvanhorn

One edit to the proposal: when v is the list of optional arguments to a function, it should also be legal to pass v as the list of optional final arguments to another function.

ksvanhorn avatar Oct 11 '18 22:10 ksvanhorn

Could user-defined Stan functions utilize the syntax and accessors for variadic C++ functions? https://en.cppreference.com/w/cpp/utility/variadic

bgoodri avatar Oct 19 '18 18:10 bgoodri

Thanks for the proposal. The approach you propose is similar to what C++ does under the hood with variadic arguments (as @bgoodri alludes to). This has been generalized type safely to heterogeneous arguments with parameter packs in C++11, but I'm not sure we need that genrality in Stan.

Given that we control the code generator, we could also implement the way we implement print() or reject() or { x1, ..., xN }, which is with a builder (create().add(x1).add(x2)...add(XN).build()).

We're going slowly on language changes as we rebuild the parser, AST, and code generator in a language that'll be easier to work with than C++/Boost Spirit Qi going forward.

Also, the method you provide is the way to implement this now, although it's clunky with the extra {...} and won't necessarily work with integer/real values, e.g.,

real foo(real[] x) {
  return sum(x) + 3;
}
real y = foo({1, 2, 3});  // FAILS TO COMPILE---no foo(int[])

When we get around to making Stan fully covariant, we'll allow int[] to real[] promotion the same way we allow int to real promotion everywhere.

bob-carpenter avatar Oct 20 '18 20:10 bob-carpenter

There is a newer stanc3 issue which is similar: https://github.com/stan-dev/stanc3/issues/1348 I'm going to close this older one in favor of it

WardBrian avatar Feb 14 '24 19:02 WardBrian