-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend Ferrum to work with async sequences #192
Comments
Yes, you are right that is a desired feature. I suppose you've already seen the existing issues #72 and #169 Basically we have more than two kind of values we have the properties
You may notice that some combinations are missing; no sort of value is
Now the trouble really is it would be nice to have some framework that would support multiple sorts of these values in an extensible way especially since there are alternate implementations of lazy values, promises, reactive sequences and it would be nice to provide support for those. Then the next rabbit hole is that some functions such as Another example, extend would make much more sense to represent with this framework: It takes a function and repeatedly applies this to some input to build up a sequence, so: I do have an idea how to do this; we can probably model this using coroutines or generator functions. Every Type implementing Sort needs to provide four things:
Then based on this we could implement a wrapper that lets us model functions operating on a pair of generators as a third generator for ease of use: const Arity = Enum(['One', 'Many']);
const [One, Many] = Arity;
const Exec = Enum(['Sync', 'Async']);
const [Sync, Async] = Exec;
const Avail = Enum(['Immediate', 'Lazy']);
const [Immediate, Lazy] = Avail;
const SortDescriptor = Tuple('SortDescriptor', ['arity', 'exec', 'avail']);
const ValueSort = SortDescriptor(One, Sync, Immediate);
const ListSort = SortDescriptor(Many, Sync, Immediate);
const SeqSort = SortDescriptor(Many, Sync, Lazy);
const PromiseSort = SortDescriptor(One, Async, Immediate);
const EventSort = SortDescriptor(Many, Async, Immediate);
const RxSort = EventSort;
const AsyncIteratorSort = SortDescriptor(Many, Async, Lazy);
const LazySort = … // function can be used to represent a lazy value
const AsyncLazySort = … // async funcion can be used…
const Sort = Trait('Sort');
Sort.impl(Array, () => ({
descriptor: ListSort,
conversionRules: dict({}), // defaults
schedConstruction: // I would have to think a while before I could build this and decide on the proper interface
schedIteration: // nor this
}));
... // Other types
const defaultConversionRules = new Map({
ValueSort: Box, // We probably need a dummy type for this that just wraps a value; might be a unary tuple or something
ListSort: Array,
SeqSort: Sequence, // Gonna introduce a sequence constructor,
PromiseSort: Promise,
EventSort: SomeNewReactiveStreamType, // Gotta build that too
AsyncIteratorSort: AsyncSequence, // Gonna
LazySort: Lazy,
AsyncLazySort: AsyncLazy,
});
const sortConversionTarget = (value, targ) => false
|| ifdef(Sort.lookupValue(value), (impl) => impl().get(targ))
|| defaultConversionRules.get(targ);
const Consume = Symbol();
const ConsumeOutsideValue = Tuple('ConsumeOutsideValue', ['Value']);
const TryConsume = Symbol();
const DeclareResult = Tuple('DeclareResult', ['Sort']);
const Produce = Tuple('Produce', ['value']);
const TryProduce = Symbol('TryProduce', ['value']);
const sortMetaFunction = (name, impl) => {
// No idea how to implement this yet but should support auto currying and all cool stuff
};
const extend = sortMetaFunction('extend', function *(inputSort, fn) {
yield DeclareResult(inputSort // Does not care if input is sync or async; output will be the same
.oneIntoMany() // Will throw if input was many or zero
.ensureLazy()); // Will be entirely ok if input was lazy
let val = yield Consume;
while (true) {
yield Produce(val);
// Uses the resolution function provided by the consumer; If the producer was async this should for
// instance resolve the promise
val = yield ConsumeOutsideValue(fn(val))
}
}); Which is to say, yes this is a desired feature but getting right is exceedingly difficult particularly in light of the sub par choices made by js. I think it boils down to bringing usable category theory to js which is scary …I wouldn't want to provide a partial solution using a simple if statement in a stable version… I was planning to introduce a Ferrum Next branch for the next major version containing some implementation of this entire concept; I think a partial solution using if statements could very well be added there. I would be interested in how well that would work…it may well be possible that I am overthinking it! |
Also take note of #138 |
Problem
Many libraries (such as database drivers) nowadays return AsyncIterator instances. I have found it to be a great abstraction for an asyncronous sequence of items, mainly because it's well integrated with the language, thanks to the
for await
,yield
, andyield*
statements.But writing the same imperative loops over and over again has the same drawbacks we know from working with regular iterators and collections. Therefore I have been factoring my code into my own hodge-podge of functional utilities (map, filter, take...) implemented using async generators and
for async
loops.It would be nice if ferrum was somehow extended to work with them, using the same universal language it has already defined for working with synchronous collections and iterators.
Proposed solution
It is well known that
Symbol.iterator
(ie. Ferrum's Sequence trait) cannot be implemented for AsyncIterator / AsyncGenerator objects. Therefore one solution would be to implement a new AsyncSequence trait, reusing the standardSymbol.asyncIterator
symbol, and extending the entire Ferrum library to work with it.For instance,
map()
would check whether its first argument implements the AsyncSequence trait and in that case return an AsyncSequence itself.The text was updated successfully, but these errors were encountered: