gorillascript
gorillascript copied to clipboard
Idea: built-in support for a "box" type
The idea is to provide an API for using and manipulating a standardized "box" interface that holds a value without being that value outright.
Although there would be a syntax for generating one of these boxes on-the-fly, the basic interface would be expected to be as such:
interface Box<T> = {
// value can be a normal property or a getter, but is never expected to be set manually
value as T
// whenever the box's value is manually being changed, the set method will be called.
// it is expected to return the new value of the Box
// if not provided, then the Box is essentially seen as read-only.
[optional] set(new-value as T) as T ->
}
Essentially, any JavaScript object can function as a read-only Box, as long as .value can be accessed.
The way it could be used in GorillaScript is with the following syntax:
let x = box! 0 // possible syntax of how to create a read-write box with a starting value
f(&x) // same as f(x.value)
&x := 1 // same as `x.set(1)`
&x += 1 // same as `x.set(x.value + 1)`
let o = { y: box! 10 }
&o.y ^= 2 // same as `let tmp = o.y; tmp.set(Math.pow(tmp.value, 2))`
This functionality could also be expanded upon by extra methods on one's Box, such as something like .on-change(func), then the following capability could open up:
let x = box! 1
let y = box! 2
let sum = formula! &x + &y
assert &sum == 3
&x += 1
assert &sum == 4
sum.on-change #(value)
update-some-dom-element value
This essentially allows reactive code at the language level.
This would be doable by making formula! walk the AST tree looking for uses of the & prefix and declaring a dependency on that box using the on-change API (or something similar).
Some things I'm not sure about:
- Whether to keep
valueas a normal property or a method. A normal property would be more efficient in the general case, but may lead to unnecessary calculations in the case offormula!if it's never accessed (which would be an atypical use case anyway) - The
on-changepseudo-API. - What syntax for value access/assignment. Currently
&would work well enough and hearkens back to C's references, but not in the same way. - A better syntax than
box!to create a typical read-write box? - Garbage collection concerns with
formula!referring to its parent while also being self-referential, thus leading to a case where the callback passed to its dependencies refers to a formula that has become otherwise unreachable, but unable to be GC'd due to the callback's reference to it.
This would also pretty much invalidate the need for #60
Now that I'm thinking about it, it would kind of make some sense to have the value be a method instead, like get().
Providing that functionality, one could also test for a Box-like object by doing is-function! o.get.
It would also allow for formulas to mark themselves as "dirty" and only update when needed.
Also, in the following example:
let alpha = box! true
let bravo = box! "hello"
let charlie = box! "world"
let formula = formula!
if &alpha
&bravo
else
&charlie
assert &formula == "hello"
&charlie := "friend" // this should not recalculate `formula`
assert &formula == "hello"
&alpha := false // this would mark `formula` as dirty
assert &formula == "friend"
That could place a dependency on alpha, which would then evaluate to bravo or charlie, only updating itself if the one actually being needed were updated, ignoring any results of the other unless alpha changed.
It should not do any recalculations if a particular Box is not referenced but whose value changes.
Also, in the event that a method rather than a normal property is used for the Box interface, another macro could be useful:
let box = lazy! factorial(6)
assert &box == 720 // only at this point is factorial(6) calculated
assert &box == 720 // uses the cached version, no recalculation.
This seems a bit like lenses, unless I don't understand fully what you're suggesting. Which provide get and set functions over immutable structures. I don't have enough experience using them to pass worthwhile judgement on the idea, but I say go for it.
Hm, do I sense monads here? :-)
For the record, I would love a better abstraction for promises / async / lazy computation. It's just that it's a huge area and requires a lot of thinking; you can't expect an immediate useful feedback in here. If you let this idea cook for several months, maybe we can come up with something good.
On a completely different note, you should probably use TypeScript notation when describing interfaces; I think it's pretty awesome:
interface Box<T> {
// value can be a normal property or a getter, but is never expected to be set manually
value: T;
// whenever the box's value is manually being changed, the set method will be called.
// it is expected to return the new value of the Box
// if not provided, then the Box is essentially seen as read-only.
set?(newValue: T): T;
}
Btw I've seen you mention reactive stuff on IRC; I've spent a lot of time building a reactive library for LiveReload 3 (which I ended up ditching, because it was requiring exponential effort to make it handle everything I needed it to handle). Would love to discuss language support for reactive stuff.
I think this is really neat and would make a worthwhile addition, though I agree with @andreyvit that it might be useful to think this through the grander scheme of async and even promises. How compatible could nesting of such constructs as formula! and async and promises be made to be? On another note, have you looked into Elm, @ckknight? It has a lot of FRP built in and could provide ideas.