idyll-lang / idyll

Create explorable explanations and interactive essays.
http://idyll-lang.org/
MIT License
2.01k stars 87 forks source link

User defined functions (UDF) #326

Closed mathisonian closed 6 years ago

mathisonian commented 6 years ago

Users should be able to inject functions into the idyll evaluation context, e.g.

[Button onClick:`myCustomFunction(x)` /]

This was mentioned in #202 and in gitter by @mhalle.

mhalle commented 6 years ago

I just wanted to elaborate a little on my use case. I am thinking about using idyll for building specialized data storytelling tools (ones designed for specific domains, like physics or CS). I would in general like to maintain the react/idyll model of controlling the story by manipulating state.

At some point, the state is going to be more complicated than discrete variables, and the operations on it will be more than built-in operations. I would like to be able to have higher level functions/methods to manipulate that state. For example, imagine a balance simulation with two weights and a balance point. I would like to have those variables in a state object, and methods to change them (e.g., "setWeight(0, 0.1)", or maybe s.weight0 = 0.1, etc). That way, for instance, I could have multiple balances to compare all active.

Right now, I can't see any way to implement those methods. I can see two possible techniques: a simple class with methods that were exposed, and something like an immutable store where methods can produce a new state object (which could be stored on a stack for undo redo).

mathisonian commented 6 years ago

It is possible to use more complex objects as variables now, for example

[var name:"weights" value:`[ 1.1, 2.3 ]` /]

or 

[var name:"weightsObject" value:`{ 0: 1.1,  1: 2.3 }` /]

and often it is useful to do more complex variable updates from within the components: using the updateProps functionality, components can push updates to the Idyll variables. However, there will be some cases in which having custom functions is just easier.

My first thought was to let users define a custom object that is accessible in the scope of the evaluated expressions, e.g.

udf.js

module.exports = {
  setWeight: (x) => {
     // do some complex calculations in here, 
     return newX;
  } 
}

you could also use require() statements in that file to access libraries, etc. Then in idyll markup it would look something like

[Button onClick:` weight = setWeight(...) ` ] Click [/Button]