Giter Club home page Giter Club logo

jupiterate's Introduction

Jupiterate

A library for performing various operations on any ES iterable (e.g. arrays, maps, sets; but also any custom iterables).

jupiterate's People

Contributors

lazarljubenovic avatar

Stargazers

 avatar

Watchers

 avatar  avatar

jupiterate's Issues

Introduce `chunkStrict`

With #13 it's obvious that we need chunkStrict at least for the types, but it will also have a slightly different runtime behavior: it should throw if the last chunk ends up having less than chunkStrict elements.

Note that it would still actually process the majority of the array and yield the first n - 1 valid chunks.

`fork`?

Something like this?

const [a, b] = j.pipe(
  [1, 2, 3, 4],
  j.map(x => x + 1),
  j.fork(
    j.pipe(
      j.map(x => x ** 2),
    ),
    j.pipe(
      j.andFinally(j.Reduce, sum, 0),
    ),
  ),

A list of operators

Types of operators

Some iterators by definition must be eager, ie. in order to emit a value they must check the whole iterable. It's generally discouraged to use these, but it's fine to use them when we know that the input array is such, or that algorithm simply demands such a step and there's no way to do it otherwise (or the developer decides that he shouldn't bother). We should agree on JSDoc tagging the ones that have to be eager with just adding @eagerOperator JSDoc tag. No need for docs yet in general, but we should start placing @eagerOperator immediately while we make them so we don't have to re-categorize later.

Also, some of these might be "ending" operators: they take an iterable and obviously return a single value, such as a number (length(), size(), sum() or something), a boolean (has(t: T), startsWith(...t: T[]) or something). I believe these are operators too as I often put reduce or find or findIndex together with things like map, filter.

Some operators should be static, as they are binary without clear order. For example, concat semantically makes sense to be a.contact(b), but join would be join(a, b). Things like j.zip(a, b) would return an iterator, instead of starting a chain, so you'd do j.pipe(j.zip(a, b)). Remember, j.pipe is just a way to improve the syntax of c(b(a(x))) to j.pipe(x, [a, b, c, d]), where we read functions as we apply them and not as we unwrap them, akin to x |> a |> b |> c |> d which might eventually be available in JS and drop the need for j.pipe, which is technically only a helper function and not the core of the library: it's a way to overcome the lack of syntax. Tag then with `@staticOperator.

Some would be higher-order operators, as they would accept an iterable as an argument. For example, concat comes to mind. Or, they might return an iterable of iterables, which can be flattened using flat or flatMap. We don't have to tag this, it's obvious from the type of the operator. Just wanted to be complete here.

The big list

I've tagged us alternately. Do yours in a PR as a whole, but leave out all that you think are complex or that you're not sure that they have to be eager. The order of the libs I looked at is random, and I never repeated a operator (unless I made a mistake). So the lists are disjoint and the following will be much shorter than the first one. The sources are here just for the reference.

Note: If for some operator you don't see the need for, because they're trivial to implement in a different way, and because that other way is much more intuitive to use, just skip them. The point of the list is to provide a starting point; we can make later remove the ones we don't think make sense, or just not make them in the first place if we're not sure, and create a separate issue.

For example, I had no idea what compact meant in lodash, until I read that it's basically filter(Boolean). Sometimes, though, an operator is just a special case of a different operator (eg. any sort of slice is basically a specialized filtering by index), and then we include it because it gives a known semantic meaning before looking at what it does (eg. takeFirst(2) is easier to read over takeWhile((_, i) => i < 2).

Lodash

RXJS

Haskell

I was randomly looking at this code: https://wiki.haskell.org/Blow_your_mind

  • transpose @lazarljubenovic
  • ensure - accepts a guard and throws? like an inline assert? @JKostov
  • iterate - looks like reverse of reduce? it starts with 1 and gives it x => 2 ** x, and then applies this over and over? indeed, seems like the alternative signature is unfoldr @lazarljubenovic

Random thoughts

Some operators have "right" variants. I can't make up my mind: should we move them as they against the "iterable" ideas, or should we add them and just mark them as eager (and thus possibly leading to infinite loop)?

Assertions and error handling

Something like this?

j.pipe(
  source,
  // definitely an `assert` namespace
  j.assert.notEmpty(),
  // maybe a few more inner namespaces for groups of related functions
  j.assert.length.atLeast(3),
  j.assert.each.satisfies(tg.isNumber), // type guards
  // not entirely sure what's needed here without some case studies
  j.catchError((error) => j.Empty()), // also not entirely sure if this is even possible
)

Assertions would be partial triggers -- they would never yield values through before understanding that the condition is satisfied. In other words, they should either behave like passThrough or throw an error. They can tweak the type information though (e.g. the satisfies from the example above.

Run test and do coverage report on each PR

Given the nature of the lib, it shouldn't be difficult (nor pointless) to maintain 100% coverage, but we'll see about that once we have it all set up. Travis? Or Actions? Disallow merges if fails?

`chunk` could be smarter about the inner `Array` in the return type

For chunkSize values known at compile-time, we we could return an union of tuples instead of a generic Array<T>. For example, chunk(3) should return Operator<T, [T] | [T, T] | [T, T, T]>.

Unfortunately, it's not possible to mark only the last item of the iterable with a different type, and I don't think TypeScript will add such a feature in the foreseeable future.

Ditch enders, rework operators/enders/static distinction

Currently, there are three types of functions:

  • operators are functions which accept an iterable and return an iterable (i.e. they are infinitely chainable/pipeable);
  • enders are functions which accept and interable and return an arbitrary thing (i.e. they can appear only at the end of a chain);
  • static functions accept an arbitrary thing and return an arbitrary thing, commonly an iterable and sometimes something related to iterables (e.g. a tuple of iterables, a map of iterables, etc).

However, enders Ender<T, U> are just a more convenient way to write static functions which accept exactly one iterable, and optionally more parameters.

For example, a static function for computing the size of an iterable has signature (iterable: Iterable<T>, options: any) => number, and is written as follows:

const count = j.s.count(j.pipe(
  [1, -1, 2, -3],
  j.filter(x => x > 0),
), options)

Note that “options“ have been added just to make the example more complete. Imagine a more complex operation. An accompanying ender would be written as follows, which is more readable:

const count = j.pipe(
  [1, -1, 2, -3],
  j.filter(x => x > 0),
  j.e.count(options),
)

Here, I propose ditching enders as separate entities and introducing a catch-all “ender” called j.andFinally which accepts a function which accepts an iterable as the first parameter, and passes the other parameters into the static function.

const count = j.pipe(
  [1, -1, 2, -3],
  j.filter(x => x > 0),
  j.andFinally(j.s.count, options),
)

With enders out of the way, we only have regular operators and static operators, which could then be distinguished by naming convention instead of by conflusing and ugly single-letter namespaces e and s.

const count = j.pipe(
  [1, -1, 2, -3],
  j.filter(x => x > 0),
  j.andFinally(j.Count, options),
)

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.