Jupiterate
A library for performing various operations on any ES iterable (e.g. arrays, maps, sets; but also any custom iterables).
A library for performing various operations on any ES iterable (e.g. arrays, maps, sets; but also any custom iterables).
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.
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),
),
),
Either using j.pipe
with explicit parameter, or in point-free style with j.compose
.
Technically it is and will unfortunately pass the type check, but it has inverse semantics: the source iterable is the joiner and the actual iterable is the first argument.
This would allow segments to be infinite.
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.
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)
.
chunk
@JKostovcompact
@lazarljubenovicconcat
@JKostovdifference
, differenceBy
, differenceWith
@lazarljubenovicdrop
, dropWhile
, dropRight
, dropRightWhile
@JKostovfill
@lazarljubenovicfindIndex
@JKostovfindLastIndex
@lazarljubenovicfirst
, head
(aliases) @JKostovflatten
, flattenDeep
,flattenDepth
@lazarljubenovicfromPairs
@JKostovindexOf
@lazarljubenovicinitial
@JKostovintersection
, intersectionBy
, intersectionWith
@lazarljubenovicjoin
@JKostovlast
@lazarljubenoviclastIndexOf
@JKostovnth
@lazarljubenovicpull
, pullAll
, pullAllBy
, pullAllWith
, pullAt
@JKostovremove
@lazarljubenovicreverse
@JKostovslice
@lazarljubenovicsortedIndex
, sortedIndexBy
, sortedIndexOf
, sortedLastIndex
, sortedLastIndexBy
, sortedLastIndexOf
@JKostovsortedUniq
, sortedUniqBy
@lazarljubenovictail
@JKostovtake
, takeWhile
, takeRight
, takeRightWhile
@lazarljubenovicunion
, unionBy
, unionWith
@JKostovuniq
, uniqBy
, uniqWith
@lazarljubenovicunzip
, unzipWith
@JKostovwithout
@lazarljubenovicxor
, xorBy
, xorWith
@JKostovzip
, zipWith
@lazarljubenoviczipObject
, zipObjectDeep
@JKostovcountBy
@lazarljubenoviceach
/ forEach
, eachRight
/ forEachRight
@JKostovevery
/ all
@lazarljubenovicfilter
@JKostovfind
, findLast
@lazarljubenovicflapMap
, flatMapDeep
, flatMapDepth
@JKostovgroupBy
@lazarljubenovicincludes
@JKostovinvokeMap
@lazarljubenovickeyBy
@JKostovmap
@lazarljubenovicorderBy
@JKostovpartition
@lazarljubenovicreduce
, reduceRight
@JKostovreject
@lazarljubenovicsample
, sampleSize
@JKostovshuffle
@lazarljubenovicsize
@JKostovsome
@lazarljubenovicsortBy
@JKostovmax
, maxBy
, min
, minBy
@lazarljubenovicsum
, sumBy
, mean
, meanBy
@JKostovcapitalize
, camelCase
, lowerCase
, snakeCase
, kebabCase
, upperCase
deburr
, startsWith
, endsWith
, padStart
, padEnd
, pad
, trimStart
, trimEnd
, trim
, truncate
, words
(strings are iterables too). For the casing ones we'll need some sort of lookahead? @lazarljubenovicempty
, never
, throw
- Does this make sense for iterators in general? @JKostovfrom
- Probably split this into multiple @lazarljubenovicrepeat
@JKostovjust
@lazarljubenovicscan
@JKostovstartWith
(is this like padStart
?) @lazarljubenoviccatch
, retry
- research how error handling works with iterators @JKostovwithDefault
, withDefaultIf
@lazarljubenovicskipUntil
, skipWhile
, takeUntil
, takeWhile
@JKostovdistinctUntilChanged
, distinctUntilKeyChanged
@JKostovI was randomly looking at this code: https://wiki.haskell.org/Blow_your_mind
transpose
@lazarljubenovicensure
- accepts a guard and throws? like an inline assert? @JKostoviterate
- 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
@lazarljubenovicSome 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)?
I'll write a parser for this 😛
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.
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?
Also probably automate a benchmark report on each PR.
Shorter, rolls the tongue better and gets the point across better.
A generalization of pairwise
. Needs a cyclic variant as well.
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.
Currently, there are three types of functions:
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),
)
Use ts-morph
to collect stuff.
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.