GithubHelp home page GithubHelp logo

a-synchronous / rubico Goto Github PK

View Code? Open in Web Editor NEW
267.0 13.0 17.0 3.67 MB

[a]synchronous functional programming

Home Page: https://rubico.land

License: MIT License

JavaScript 98.59% HTML 0.32% TypeScript 0.49% Roff 0.60%
function-composition asynchronous transducers parallel series functional-programming promise async-await node javascript

rubico's People

Contributors

fredericheem avatar kalagin avatar lmulvey avatar lmulveycm avatar richytong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rubico's Issues

curry

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

transform(...)(Observable)

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.

slice

It may be convenient to have a slice functionality like Array.prototype.slice.

slice(2, -1)([1, 2, 3, 4, 5]) // > [3, 4, 5]

benchmarks

primarily the transformation functions, as the async handling in the at the moment can get spotty. see this question

convert map.series to async/await

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

doc.rubico.land

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

Test robustness

The tests can use some help. In particular, testing more edge case values like null and undefined.

transform; initial value is mutable

> 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 () => []

Feature requests are welcome

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.

get(path, defaultValue); defaultValue can be a function (lazy evaluation)

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 })

CI

Each pull request should run tests automatically. There are no containerized dependencies, so this should be relatively simple to set up.

map.withKey

map.withKey

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

  • f is asynchronous
map.withKey((number, key, obj) => {/* stuff */})({
  a: 1, b: 2, c: 3,
})

TODO

  • - implementation
  • - tests

better error messages

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.

TypeScript examples

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.

v1.2 line items

  • - pick.range; like slice, pick.range(0, -1)
  • - map.sleep; map.sleep(1000, fn) fn(x), wait 1sec, fn(x1), wait 1sec, fn(x2), ... fn(xN)

Benchmarks

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].

v1.1 todos

  • map.pool async limit tests
  • map.withIndex implementation and tests
  • transform(...)({}) object transform
  • #6
  • map.pool error tests

uniq

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' }]

rubico/x/sleep

must use tap

sleep

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

guidelines

rubico/x/inspect

must use map

inspect

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

rubico/x/flatten

please use flatMap in the implementation

flatten

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

rubico/x/ package integrity tests

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

ESM + CommonJS single code source

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.

rubico/x/ternary

must use switchCase

ternary

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

transform + reduce async iterable cancellation

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

rubico/x/ browser exports

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.

inner function Error tests

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

  • - pipe - @richytong
  • - fork - @richytong
  • - assign - OPEN
  • - tap - OPEN
  • - switchCase - OPEN
  • - map.series - OPEN
  • - map.withIndex - OPEN
  • - filter.withIndex - OPEN
  • - reduce - OPEN
  • - transform - @richytong
  • - any - OPEN
  • - all - OPEN
  • - and - OPEN
  • - or - OPEN
  • - not - OPEN
  • - eq - OPEN
  • - gt - OPEN
  • - lt - OPEN
  • - gte - OPEN
  • - lte - OPEN

rubico/x/last

please use get in the implementation

last

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

flatMap

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
flat-map

flatMap

y = flatMap(f)(x)
f is a function
x is an Iterable, AsyncIterable, or reducer function

  • elements of x are Iterable or AsyncIterable

f 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 AsyncIterable

TODO:

  • - flatMapArray - richytong
  • - flatMapSet
  • - flatMapReducer

npm run ts-compile

run compiler for TS files that allows use in browser and node environments

reduce.right

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' }

rubico/x/log

last

log to the console

y = log(...messageArgs)(x)

...messageArgs can be anything, including functions

  • if any messageArg is a function, it is evaluated with x
    x is anything
    y is x
log('hello', 'world')({ a: anything }) // { a: anything }
// > hello world

TODO

rubico/x/

introduction

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/

  • must use rubico
  • must work in node environment
  • must include a documentation entry following this format

METHOD

DESCRIPTION

// SIGNATURE

RULES

// EXAMPLE

see existing /x/ for examples

map.series.withIndex

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]]

rubico/x/promisifyAll

must use map

promisifyAll

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

style guide

linebreaks

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
}

functions

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

reduce(f, init); transform(f, init); init can be a function

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

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 asyncGenerators should be immutable

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.