probmods / webppl

Probabilistic programming for the web
http://webppl.org
Other
615 stars 89 forks source link

Allow webppl functions to be called using member expressions #358

Open stuhlmueller opened 8 years ago

stuhlmueller commented 8 years ago

For complex programs, it is useful to bundle up functions into objects. For example, we might want to represent agents as follows:

var agent = { 
  act: function(state){...}, 
  expectedUtility: function(state, action){...},
  params: ...
};

Currently, we first have to reassign methods to use them (var act = agent.act; act(...);) so that they don't get interpreted as primitive function calls. This makes the resulting models less readable. There should be a way to directly call webppl functions as methods.

Here are two possibilities that come to mind (I'm open to others):

  1. We could do lexical analysis to determine whether an (object) variable is bound within a webppl program; if it is, any method calls will be interpreted as compound calls, not primitives.
  2. We could have a macro that marks objects for which method calls will be interpreted as compound. This could be something home-baked, or we could use the ES6 class syntax and decide that all classes defined in this way will support method calls.
null-a commented 8 years ago

Another possibility (which I'm sharing, but don't know is a good idea) would be to make this work by using something other than member expressions for our foreign function interface. This is probably a bigger change than the first 2 suggestions, but I think it has at least some merit.

  1. I wonder if it might be easier to use the language if we make it a little more obvious where the boundary between webppl and JS lies. In particular I'm thinking about people who are new to the language. I wonder whether once someone knows you can seamlessly call JS functions from webppl, it's likely they'll make the inference that the two interoperate seamlessly in general, and assume you can pass/call anything you like across the boundary. I might have seen this happen once, though those of you who have taught webppl will have a much better idea about this than I do of course!
  2. As we write more packages the current approach of adding all webppl functions from all loaded packages to the top-level seems likely to break down. There are other ways to handle this, but perhaps in the future calling webppl functions via member expressions will be common enough to dedicate the member expression syntax to it.
stuhlmueller commented 8 years ago

Rethinking the FFI is a good idea and should be done before 1.0. I also agree that adding all webppl functions from packages to the top-level is not a long-term solution. (This would likely come up when we rethink require/import anyway.)

For now, a good next step would be to make a comprehensive list of all of the member expression calls that we have been using (in examples, dippl, forest, etc). Then we can think about what each instance would look like under different proposals.

stuhlmueller commented 8 years ago

A workaround that may improve code readability in some situations:

var call = function(method, arg1, arg2, arg3) {
  return method(arg1, arg2, arg3);
}

var world = {
  transition: function(x) {return x + 1;} 
};

// This doesn't work:
// world.transition(1)

// This works:
call(world.transition, 1)
chrisranderson commented 8 years ago

Ran into this here: https://gist.github.com/chrisranderson/ec5151619da3aaeab9e0f1327838ed8c#file-dp-js-L80. I wanted to make a more general function that took functions as part of a configuration. I guess you know, but the same thing happens when you have an array with a function in it.

Should your call solution be put in the header and documented (btw thanks! worked great)?

dritchie commented 8 years ago

Here's a thought, to keep the conversation going: wrap all deterministic JS functions in a webppl shim that handles continuations/stores/addresses.

It's easy to do this for all deterministic JS provided in webppl packages (we can apply the shim upon loading the package).

For builtins, it's not so clean. Ideally, we'd want to know what all the builtins are, and then mask those with shim-ed versions scoped to the current webppl env. Off the top of my head, options for catching all the built-ins include:

longouyang commented 7 years ago

Here's an idea for a new FFI syntax: we use {{{ and }}} to delimit sections of code that will not receive the CPS transform.

e.g.,

{{{ _.groupBy(people, function(person) { return person.occupation }) }}}

This would address two issues with the current FFI:

  1. It'd make it possible to use higher-order helper functions in external libraries (e.g., underscore) because the function-valued arguments would no longer get CPS-transformed.
  2. We could write in a more object-oriented style (e.g., agent.act(...)) because the dot no longer means "we're calling a foreign function"

This doesn't exactly solve the issue in #642 (summary: by default, parseFloat is broken) but maybe we could address that applying the CPS transform to a list of JS builtins.