Open mstade opened 10 years ago
I feel this needs some expansion:
Using sequences like this implies that the sequence is fully realized before the execution is resumed. This may be magical, and some care might be required to deal with it.
What I mean by this, is that something like yield take(4, chan)
(no pun intended) will immediately return a seq
. Grabbing first
in this case would then yield a future, since chan
is a channel. Presuming this is in a go
block, the go
function would then realize the sequence, and for any future it finds attach a callback to retrieve the value. Once all futures have been realized, the go
block would call back to the generator, giving back a new seq
with all values that were futures being realized into actual values.
Essentially this means the go
block would only resume the generator once all values that were yielded have been realized. In the case a yielded value is a seq
, it means realizing the whole seq (bummer if its infinite.) I think this is the only way to do this, while still having the nice syntax of suspending execution.
It might be tricky to implement take/put/filter/map etc in such a way that they are seq/chan agnostic (which is to say, they are sync/async agnostic) but it's worth researching how that can be done.
@sammyt what should happen when you do this?
Seq = defprotocol({
first : []
, rest : []
})
const nums = [1, 2, 3]
Seq(nums, { // Notice the first argument is the `nums` array, not a "type"
first: function(arr) { return arr[0] }
, rest: function(arr) { return arr.slice(1) }
})
Seq.first(nums) // 1
Seq.rest(nums) // [2, 3]
Seq.first([3, 2, 1]) // ???
Should defprotocol
pick the "type" of the object, (i.e. getPrototypeOf
) and associate the protocol implementation to that, or associate the protocol implementation with the actual object itself? I'm inclined to say the latter, because it's less magical, but I don't know.
Or should defprotocol
be "clever" enough to recognize non-type objects (can it?) and simply balk at them? A danger with the whole getPrototypeOf
story is that if of course that you may just end up defining implementations for things you didn't intent, such as Object
meaning now everything will probably match (since Object
is the type of everything, mostly.)
Should defprotocol pick the "type" of the object, (i.e. getPrototypeOf) ... or associate the protocol implementation with the actual object itself?
I also would say the object itself, else to much magic
How would I define the protocol implementation for the following "data structure"
var Node = function(data, left, right){
this.data = data
this.left = left
this.right = right
}
Such that any new Node(...)
had a defined Seq
implementation?
Presumably, it'd be something like this:
Seq(Node, {
first: function(n) { return n.data }
, rest: function(n) { return n.right }
})
var someNode = new Node(1)
Seq.first(someNode) // 1
Seq.rest(someNode) // undefined
When invoking, we'd do an is
check on the first argument, which would then see that someNode is an instance of Node
and thus match that. If we just use is
it'll work nicely with both instances and "types", since it favors strict equality over anything else.
Not sure about using the protocol itself as the implementation mechanism...
If you wanted a tree view out of your node, you could have some syntax like this:
var tree = Seq(someNode)
tree.first() // 1
tree.rest() // undefined
Basically, any protocol given two arguments means implementation; given just the one argument would mean a wrapped instance which is just sugar for Protocol.method(instance, ...)
– make sense?
That's a nice and presumptuous implementation that'll let you traverse any tree in any direction, so long as it's right. :o)
Build fails because travis-ci/travis-ci#3108. Change config to use iojs whenever that gets sorted, or change to Wercker or something.
Travis added support for iojs finally, and so all is suddenly well. :o)
Starting this PR as a WIP on channels. This work will likely also include – or depend on – protocols, multiple/predicate dispatch, and futures.
Channels are a nice way to provide an abstraction for processes communicating with one another. They act a lot like sequences, with the exception that values may not be immediately available. As such, there's a need to find a useful abstraction for future values. Promises as they are specified in ES6 are an over engineered mess, and while they may well be supported as return values for non-blocking channels, they probably shouldn't be the recommended mechanism.
One thing I'd like to accomplish with channels is to make them act largely the same as sequences – that is, they are a view on to a data structure. The main difference however, is that sequences are blocking and immediate, so when realizing a value (i.e. calling
rest
orfirst
) that value is immediately available. Of course, the value may be a future, meaning the consumer will have to add a function to be called once the future value is available. (Which may be immediately, in which case the future is effectively the same asconstantly
.) The benefit of this is that function such astake
,map
, etc. works regardless of what, where, or when the structure is available. To make for nice syntax, something similar to Clojure'sgo
blocks make sense. Here's a contrived mockup:In this example,
tick
andlog
are channels that are used ingo
block which takes care of the boilerplate of dealing with futures returned fromyield take(1, tick)
. Using sequences like this implies that the sequence is fully realized before the execution is resumed. This may be magical, and some care might be required to deal with it. Here's another example, where an infinite channel is used:The imaginary
draw
function is used to output pixels on to a canvas (this, as it were, could also be a channel, but I'm choosing to not go the shiny-hammer route just yet) by virtue of taking 4 points and making a Bézier curve out of them.In both of these mockups, generators are used to suspend execution. Since funkis is largely a research project, I don't think there's much need to limit it to ES5 environments. Should that be the case however, it may be worth looking into something like degenerate.
A benefit to using something like
go
blocks is that routines can be made into values to pass around and compose. Here's an expansion of the drawing mockup above:This is a mockup, so there are a bunch of assumptions made (like, what's
random
,draw
etc.?) but it ought to show the general direction in which I hope this work will go. The idea is to have a simple abstraction of a sequence of values, where not only is the underlying data structure not important but neither is the point in time when values are available.Work in progress, so things are bound to change, but this marks a start.