a-synchronous / rubico Goto Github PK
View Code? Open in Web Editor NEW[a]synchronous functional programming
Home Page: https://rubico.land
License: MIT License
[a]synchronous functional programming
Home Page: https://rubico.land
License: MIT License
right now, no functions are curried. I want to be able to use a function like map
like
const square = x => x ** 2
map(square, [1, 2, 3, 4, 5])
internally we would do something like
const map = _curry([1], oldMap)
// the [1] is for specifying the number of arguments at each partially applied level
the thing that makes this curry a bit different than a curry like ramda's is rubico functions validate input as they are passed arguments.
for example, pipe([add])
checks the type of add
, and throws a TypeError if it is not a function.
I want a special internal curry function that allows for this cascading validation.
Note: rethink this entire thing because reduce
I like the idea of the Observable, so let's give people a way to consume it.
Perhaps special logic is needed here, but there should really be [Symbol.asyncIterator]: ...
on Observables.
It may be convenient to have a slice functionality like Array.prototype.slice
.
slice(2, -1)([1, 2, 3, 4, 5]) // > [3, 4, 5]
primarily the transformation functions, as the async handling in the at the moment can get spotty. see this question
Right now, map.series
is synchronously creating a chain of Promises. This could potentially grow very large, so I would like to break out to async/await if there is a Promise in the mix.
Note: map.series
should still return a synchronous value if everything is synchronous
I'd like a modular TypeScript version of this existing JS example.
export should be floatingPointAverage
please also include the stackoverflow link commented at the bottom
TODO
break out docs to a nice site under this domain.
const {
pipe, fork, assign,
tap, tryCatch, switchCase,
map, filter, reduce, transform,
any, all, and, or, not,
eq, gt, lt, gte, lte,
get, pick, omit,
} = rubico // each one of these functions is a link
// option to yank this
/
- the API header
/METHOD
- base route for a method, get here from /
/METHOD#SECTION
- jump to a section on the method page
The tests can use some help. In particular, testing more edge case values like null
and undefined
.
see this question
it would break down for an iterable of infinite length.
Write a dev.to article on it eventually.
> transform(map(square), [])([1,2,3])
[ 1, 4, 9 ]
> const squareAll = transform(map(square), [])
undefined
> squareAll([1,2,3])
[ 1, 4, 9 ]
> squareAll([1,2,3])
[ 1, 4, 9, 1, 4, 9 ]
Only occurs on reduce/transform function reuse
Copying initival value will fix. This one ties in pretty closely with #60
[]
should imply () => []
Please request features here. Please also pair your ask with why you want the feature. If it makes sense to me, I will approve it.
For error throwing or defaulting with something on the payload. Careful with the stack trace on this one.
Documentation amendment
defaultValue is anything but a function and is optional
if defaultValue is a function, y
defaults to defaultValue(x)
on path not found.
Usage
get('a.b.c', () => {
throw new Error('cant get a.b.c'),
})({ d: 5 })
get('a.b.c', get('d'))({ d: 5 })
Each pull request should run tests automatically. There are no containerized dependencies, so this should be relatively simple to set up.
like map
, but call the callback with key and reference to the object in addition to the item
y = map.withKey(f)(x)
f
is a mapping function of length 3 (item, key, originalObjectRef)
x
is an Object
y
is the linear transformation of x
with f
y
is a Promise if any of the following are true
map.withKey((number, key, obj) => {/* stuff */})({
a: 1, b: 2, c: 3,
})
TODO
rubico throws TypeErrors at least once per method. Right now, these TypeErrors have error messages that look like
throw new TypeError('reduce(...)(x); x cannot be empty')
I would like to rename x
in these situations to something more appropriate. This would vary from function to function.
rubico is compatible with TypeScript, right now there's not enough examples. All examples should go in the examples
directory. All examples should be runnable, with output that makes sense.
Here is one I did: squaredOdds.ts
I plan to document the examples in the near future.
Of course, more JavaScript examples are always welcome.
Also, please include any relevant links (if any) in the example source.
pick.range(0, -1)
map.sleep(1000, fn)
fn(x), wait 1sec, fn(x1), wait 1sec, fn(x2), ... fn(xN)Benchmarks will go in the benchmarks/benchmark-name.js
.
All benchmarks should be written for use with mocha
I would like benchmarks for any exposed method in this library. You are free to choose any method[s].
map.pool
async limit testsmap.withIndex
implementation and teststransform(...)({})
object transformmap.pool
error testsrubico pairs very well with AWS Lambda and Google Cloud Functions. We need examples.
this is just an exploration
uniq(identity)([3, 1, 1, 2, 2, 3, 3]) // > [3, 1, 2]
uniq(pipe([
get('name'),
s => s.toLowerCase(),
]))([
{ name: 'George' },
{ name: 'george' },
{ name: 'Jane' },
]) // > [{ name: 'George' }, { name: 'Jane' }]
must use tap
asynchronously waits a set period of time
y = sleep(duration)(x)
y
is x
y
is always a Promise that resolves in duration
milliseconds
pipe([
() => console.log('hey'),
sleep(1000), // wait for 1s
() => console.log('ho'),
])
TODO
something like this https://tour.golang.org/list
harsh also has nice ones https://gentlekotlin.com/ https://kotlin.cs.illinois.edu/lessons/preliminaries/
must use map
get a human readable string representation of anything
y = inspect(x)
x
is anything
y
is the human readable, screen optimized string representation of x
console.log(1) // > 1
console.log(inspect('hey')) // > 'hey'
console.log(
inspect({ a: 1, b: [2], c: 'hey' }), // > { a: 1, b: [2], c: 'hey' }
)
console.log(
inspect([[1, 2, 3], [4, 5, 6], { a: 1 }]),
) /* > [
[1, 2, 3],
[4, 5, 6],
{ a: 1 }
]
*/
TODO
please use flatMap
in the implementation
Create a new collection with all sub-collections concatenated
y = flatten(x)
x
is any Iterable or AsyncIterable
y
is the concatenation of all sub-collections of x
console.log(
flatten([[1], [2], [3]]), // > [1, 2, 3]
)
TODO
I am here to help. If you have any questions with rubico, please ask them here.
I need two test files; one for CommonJS integrity with require
and one for TS integrity with import
x/
test-package-integrity-cjs.js
test-package-integrity-esm.js
both test files should test the codegen and that exports are exporting correctly
Right now the implementation is duplicated between index.js
and rubico.js
. This was my monkeypatch to support both CommonJS and ESM. I need help to remove this duplicate and still support both types of exports.
My current thoughts on this are to look towards babel. There is also the esm module on npm
, but I want to stay dependency-free. I am willing to add to dev-dependencies, however. I am not sure if esm would just be a dev depedency as I haven't looked into it that far.
must use switchCase
like switch
, but takes exactly three functions sans array
y = ternary(ifFunc, doFunc, elseFunc)(x)
ifFunc
is a predicate function that determines execution of doFunc
or elseFunc
doFunc
is a function that is executed on ifFunc
truthy
elseFunc
is a function that is executed on ifFunc
falsy
y is either doFunc(x)
or elseFunc(x)
depending on ifFunc(x)
const isOdd = x => x % 2 === 1
ternary(
isOdd, () => 'odd',
() => 'even',
)
TODO
this issue names transducers as a workaround for early cancellation of async iterables. Transducer enabled functions reduce and transform are in a great position to cancel async iteration because they control the for await...of
loop that started the async iteration
async iterable cancellation is desirable for example in front-end applications where you may be streaming data at a time and when the user navigates away you should not care about streaming data anymore and cancel at that point.
I like this API
const myOngoingTaskPromise = transform(f, null)(myInfiniteSource)
myOngoingTaskPromise.cancel() // like Bluebird
This section should link articles about rubico.
If you have an article about rubico, please make a pr and add it here.
Please also add your github username to the end.
Practical Functional Programming in JavaScript - Why it's worth it - richytong
rubico simplifies asynchronous code ๐ - richytong
I'm setting up TypeScript testing for /x/
and basically TypeScript for rubico now.
right now rubico/x/ functions only work in node. I'd like to be able to import any one of them in browser environments via scripts
<script src="https://unpkg.com/rubico/x/trace.js"></script>
this would export global trace
A possible solution is RequireJS.
We need to make sure Errors are getting thrown appropriately and with good stack traces.
Example tests for pipe
it('catches errors thrown from inner function', async () => {
assert.throws(
() => r.pipe([hi, hi, x => { throw new Error(`throwing ${x}`) }])('yo'),
new Error('throwing yohihi'),
)
})
it('catches errors rejected from inner function', async () => {
assert.rejects(
() => r.pipe([hi, hi, async x => { throw new Error(`throwing ${x}`) }])('yo'),
new Error('throwing yohihi'),
)
})
Every function should have one sync and one async thrower test.
Please base your branches off of inner-function-error-tests
List of functions
please use get
in the implementation
get the last element from a collection
y = last(x)
x
is an array
y
is the last item of x
last([1,2,3]) // 3
TODO
Why: flatMap
gives that one-to-many mapping I so desperately desire. It would greatly simplify a ton of transformations. It's like the missing piece. It would change this
/*
* vendors => Map { record_uLID -> Set { vendor_uLID } }
*/
const createRecordToVendorMap = pipe([
map(vendor => pipe([ // for each vendor of vendors
get('productVariations'),
map(pipe([ // for each productVariation of productVariations
get('variationOptions'),
reduce(map(fork({ // for each record of each variationOption of each variationOptions, create a new object
record_uLID: get('record_uLID'),
vendor_uLID: () => vendor.vendor_uLID,
}))(incMap), new Map()), // reduce the new object via incMap into a new Map()
])),
reduce(combineMaps, new Map()), // combine array of Maps into one Map
])(vendor)),
reduce(combineMaps, new Map()), // combine array of Maps into one Map
])
To this
// normal version
const createRecordToVendorMap = pipe([
flatMap(vendor => pipe([
get('productVariations'),
flatMap(pipe([
get('variationOptions'),
map(fork({
record_uLID: get('record_uLID'),
vendor_uLID: () => vendor.vendor_uLID,
})),
]))
])(vendor)),
reduce(incMap, new Map()),
])
// transducer version
reduce(flatMap(vendor => pipe([
get('productVariations'),
flatMap(pipe([
get('variationOptions'),
map(fork({
record_uLID: get('record_uLID'),
vendor_uLID: () => vendor.vendor_uLID,
})),
]))
])(vendor))(incMap), new Map())
flatMap
can mux async iterables
flatMap
would go under transformations. It would have similar behavior to Array.prototype.flatMap.
Here's a nice reference from martin fowler
y = flatMap(f)(x)
f
is a function
x
is an Iterable, AsyncIterable, or reducer function
x
are Iterable or AsyncIterablef
is applied to the elements of x
in parallel
y
is the one-to-many, map then flatten transformation of x
with f
y
is a Promise if any of the following are true
f
is asynchronous and x
is not an AsyncIterableTODO:
run compiler for TS files that allows use in browser and node environments
hook up linter
viz/benchmarkReduceAsyncIterable.png
viz/README.md
reduces from the right
works on Array
and String
should work for sync and async provided functions.
should return a sync value if provided function is sync
reduce.right((a, b) => a.concat(b), [])([1, 2, 3, 4, 5]) // > [5, 4, 3, 2, 1]
reduce.right((a, b) => a + b)('12345') // > '54321'
reduce.right((a, b) => Promise.resolve(a + b))('12345') // > Promise { '54321' }
log to the console
y = log(...messageArgs)(x)
...messageArgs
can be anything, including functions
x
x
is anythingy
is x
log('hello', 'world')({ a: anything }) // { a: anything }
// > hello world
TODO
Additions to this list are welcome and encouraged!. If you have a suggestion, open a new issue as rubico/x/your-suggested-module
.
project structure
x/
your-util-here.js
your-util-here.test.js - uses mocha, run this with `npm test`
Requirements for modules in rubico/x/
DESCRIPTION
// SIGNATURE
RULES
// EXAMPLE
see existing /x/ for examples
this should combine the asynchrony of map.series
and the indexing of map.withIndex
map.series.withIndex((item, i, arr) => {
return [item, i]
})(['a', 'b', 'c']) // > [['a', 0], ['b', 1], ['c', 2]]
must use map
promisify all values of an object, including those on the prototype
y = promisifyAll(x)
x
is an object
y
is an object of the same shape as x
with all values promisified
const myModule = { a: cb => { cb('hey') }
const myPromisifiedModule = promisifyAll(myModule)
myPromisifiedModule // > { a: () => Promise.resolve('hey') }
TODO
linebreaks only between global definitions
// good
const isDefined = x => x !== undefined && x !== null
const isNull = x => x === null
// bad
const isDefined = x => x !== undefined && x !== null
const isNull = x => x === null
// good
const doStuff = () => {
const a = 1
return a + 2
}
// bad
const doStuff = () => {
const a = 1
return a + 2
}
arrow functions when possible
x => x // good
function(x) { return x } // bad
avoid parens when possible
x => x // good
(a, b) => a + b // good
(x) => x // bad
implicit return when possible
x => x // good
x => { return x } // bad
Right now, init
is a non-function value that I'm copying. I'd like the possibility for init
to be a function. The init
function would be a unary function that takes the current data payload and returns an initial value to be reduced. init
can be async.
reduce(
(a, b) => a + b,
numbers => numbers[0],
)([1, 2, 3, 4, 5]) // 1 + 1 + 2 + 3 + 4 + 5
reduce(
(a, b) => a + b,
async numbers => numbers[0],
)([1, 2, 3, 4, 5]) // Promise { 1 + 1 + 2 + 3 + 4 + 5 }
this is important for issues like this
sort
is a transformation. it should take a binary function that returns an integer
sort
should work on any ordered collection.
sort((a, b) => a > b ? 1 : -1)([3, 1, 4, 2, 5]) // > [1, 2, 3, 4, 5]
it doesn't make sense for sort to work with transducers.
generators and generated iterators should be immutable. right now they are not; methods like fork
deplete the same shared iterator.
const generateIncrements = n => function*(s) {
for (let i = 0; i < s.length; i += n) {
yield s.slice(i, i + n)
}
}
fork([
reduce(add),
])(generateIncrements(2)('aaaaaaaaaa')) // => ['aaaaaaaaaa']
fork([
reduce(add),
reduce(add),
])(generateIncrements(2)('aaaaaaaaaa')) // TypeError: reduce(...)(x); x cannot be empty
We should have seen ['aaaaaaaaaa', 'aaaaaaaaaa']
I believe we can do this by accessing a generator's Symbol.iterator
or Symbol.asyncIterator
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.