We should talk about this at the next Stan meeting I'm at
(not this Thu but next Thu).
More inline.
On Feb 23, 2014, at 2:37 PM, Daniel Lee <
bea...@alum.mit.edu> wrote:
> Thanks, Bob!
>
> Function Block
> * Looks great.
> * Can we consider including other files? I'm envisioning a set of functions that someone might want to reuse / distribute. It'll essentially be allowing users to extend the language as a user base without hacking into Stan itself.
>
The easiest route would be to write a pre-processor
that read the files and concatenated them all together
to be passed into the parser.
The question is how we specify paths. Right now, we have
the inconvenience that everything has to be run from
the home directory of Stan, so I suppose a relative path to
that would make sense? Or we might do something clever with
stripping apart command line arguments to make it relative
to the file being processed?
> Syntax of Function Declarations and Definitions
> * Looks great.
> * I'm thinking that down the line we'd want to provide some way to debug functions. I know there are print statements and exceptions, but one of these days, we should really give a way for people to call their functions stand-alone and in a testing framework. I don't think this affects the design of the functions, but maybe the generated code?
I'm not sure most of our users will want to interact with
the C++ side alone.
Any idea how we'd do something like standalone testing of functions?
Ideally we'd have a nice unit testing framework, but that would require
a way to call the functions. I could imagine writing something in the
simple Stan language that compiles to C++ and including some test
functions. So users would have something like a test block after functions
that could optionally be called.
If we want to make data in scope, then the functions won't be
standalone, but will have to be coded as member functions on
a model. It's one argument for
>
> Container Sizing in Argument and Return Types
> * +1 for declared sizes (first option)
>
I'm really surprised everyone wants that option. Certainly
easiest to implement. But it's not like the way either Eigen
or C++ writes functions to deal with containers, which contain
their own sizing information. (And our variables all have their
own sizing built in because they're either std::vector or Eigen::Matrix.)
> Variable Scope and Local Variables
> * Looks good.
>
> Return Statements
> * Thanks for working it out. This is trickier than I imagined.
> * Last bullet: what if we threw a fatal exception instead? Perhaps something like "User function is not specified properly. foo(x,y,z) with x=...,y=...,z=... does not return a value."
We could do that by appending a final return statement and deal with
the "function not reachable" warning messages we'll get under compile for when they
do specify returns. Or we could do the full analysis of functions to only add
it if there's not a final return somewhere (that is, look at the end of each
statement and add an exception throw if there's not a return). I'd think rather
than doing that, we might just want to do a test at compile time and have them
rewrite the function.
> Input and Output Validation
> * My guess is that not allowing constrained inputs / outputs is easier. Is that true?
It's not only easier, it goes along with how we've done functions
already. You'll note that things like bernoulli_log don't take
constrained inputs.
It's partly because at runtime, the C++ translation of a Stan type
is unconstrained. That is, a real<lower=0,upper=1> just translates
to a double type.
We could have constrained inputs or outputs in the function specs and
then automatically enforce those constraints. But you'd still be able
to pass in any old expression, not just a variable declared as that type.
That is, if I had a function declared
real foo(real<lower=0,upper=1> theta);
then I could do something like this:
real x;
real y;
...
y <- foo(x);
and then foo() would check that x is between 0 and 1.
> * If that's true, my suggestion is to start by implementing that first. Allowing constrained variables looks like an extension to the language and would be backwards compatiable. We could get it out with no constrained variable declarations, and if that ends up hindering us(ers), we could work on constrained inputs / outputs.
>
It's not that big a deal one way or the other. The code
to do all the constraint checks is there already.
> Function application is an expression
> * Looks good.
>
> Subroutines
> * Are these going to be declared in the same block as functions? From Marcus's and Michael's comments, it sounds like it should before the model block. Seems like a weird place to insert some code, though...
I was planning on having everything up top and not depending on
data. So yes, that was my proposal.
One problem with allowing dependence on data variables (not hard
to implement with members) is that we'd probably want to allow functions
to be called in transformed data and allow them to be defined in terms
of transformed data. So you'd want to allow functions to be declared before
trnasformed data AND after transformed data.
I have a pretty strong preference to just put them all at the top and not
let them depend on data. That would also make standalone debugging a lot
easier.
> * I think if we had these in a separate block, we could get around the necessity of having to specify the return type of "void," but maybe I'm not seeing things clearly.
Let's just forget I ever mentioned "void". It's not necessary
no matter where they go. It was just there in my mind for symmetry
with the C++ that gets generated. But I can generate it in C++ w/o
having it be there in Stan.
> * Would it make sense to have a named subroutine (or "submodel") declared inside the model block? I'm looking at your example and think it really does belong within the model block, but I might be looking at this really shortsided.
I think of the functions as more outside the whole
model --- as you say, we may want to include them.
No reason to put them in the model block. The model
block will use them, but I don't see any reason to declare
or define the functions or submodels there.
- Bob