diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b37beeac..1fca893c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +11.1.0 + +- Add `R.filterMap` - similar to Ruby `filter_map` + +- Add `R.mapChain` - when in `R.pipe` there are several `R.map` one after the other, then `R.mapChain` can be used instead. + +- Add `R.middle` - equal to `R.init` + `R.tail` + +- Add `R.random`, `R.shuffle`, `R.switcher`, `R.sum`, `R.delay` - imported from `Rambda` + +- Add index to `R.filter`/`R.reject` predicate signiture + +- Improve typing of `R.init`, `R.tail` + 11.0.1 - Add missing JS change for `R.includes` and `R.excludes` methods in `11.0.0` release. diff --git a/README.md b/README.md index efed3f30d..d2af17fd0 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ export function addProp(key, value) { Tests ```javascript -import { addProp } from "./addProp.js" +import { addProp } from './addProp.js' test('happy', () => { const result = addProp('a', 1)({ b: 2 }) @@ -325,8 +325,8 @@ export function addPropToObjects ( Tests ```javascript -import { pipe } from "./pipe.js" -import { addPropToObjects } from "./addPropToObjects.js" +import { pipe } from './pipe.js' +import { addPropToObjects } from './addPropToObjects.js' test('R.addPropToObjects', () => { let result = pipe( @@ -1941,6 +1941,45 @@ describe('R.defaultTo', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#defaultTo) +### delay + +```typescript + +delay(ms: number): Promise<'RAMBDA_DELAY'> +``` + +`setTimeout` as a promise that resolves to `RAMBDA_DELAY` string after `ms` milliseconds. + +
+ +All TypeScript definitions + +```typescript +delay(ms: number): Promise<'RAMBDA_DELAY'>; +``` + +
+ +
+ +R.delay source + +```javascript +export const RAMBDA_DELAY = 'RAMBDA_DELAY' + +export function delay(ms) { + return new Promise(resolve => { + setTimeout(() => { + resolve(RAMBDA_DELAY) + }, ms) + }) +} +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#delay) + ### descend ```typescript @@ -2120,7 +2159,7 @@ drop(howMany: number): (list: T[]) => T[]; R.drop source ```javascript -export function drop(howManyToDrop, ) { +export function drop(howManyToDrop) { return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) } ``` @@ -3529,7 +3568,7 @@ filter( predicate: BooleanConstructor, ): (list: T[]) => ExcludeFalsy[]; filter( - predicate: (value: T) => boolean, + predicate: (value: T, index: number) => boolean, ): (list: T[]) => T[]; ... ... @@ -3597,14 +3636,26 @@ const list = [1, 2, 3] describe('R.filter with array', () => { it('within pipe', () => { - const _result = pipe( + const result = pipe( list, filter(x => { x // $ExpectType number return x > 1 }), ) - _result // $ExpectType number[] + result // $ExpectType number[] + }) + + it('with index', () => { + const result = pipe( + list, + filter((x: number, i: number) => { + x // $ExpectType number + i // $ExpectType number + return x > 1 + }), + ) + result // $ExpectType number[] }) it('complex example', () => { @@ -3643,8 +3694,8 @@ describe('R.filter with array', () => { const filterBar = (x: T): x is Bar => { return typeof (x as Bar).b === 'string' } - const _result = pipe(testList, filter(filterBar)) - _result // $ExpectType Bar[] + const result = pipe(testList, filter(filterBar)) + result // $ExpectType Bar[] }) it('narrowing type - readonly', () => { @@ -3659,14 +3710,14 @@ describe('R.filter with array', () => { const filterBar = (x: T): x is Bar => { return typeof (x as Bar).b === 'string' } - const _result = pipe(testList, filter(filterBar)) - _result // $ExpectType Bar[] + const result = pipe(testList, filter(filterBar)) + result // $ExpectType Bar[] }) it('filtering NonNullable - list of objects', () => { const testList = [{ a: 1 }, { a: 2 }, false, { a: 3 }] - const _result = pipe(testList, filter(Boolean)) - _result // $ExpectType { a: number; }[] + const result = pipe(testList, filter(Boolean)) + result // $ExpectType { a: number; }[] }) it('filtering NonNullable - readonly', () => { @@ -3778,6 +3829,104 @@ describe('R.filter with array', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#filterAsync) +### filterMap + +```typescript + +filterMap( + fn: (value: T[number], index: number) => U, +): (data: T) => Mapped> +``` + +Same as `R.map` but it filters out `null/undefined` if returned from functor functions. + +> :boom: This function doesn't work with objects (use R.mapObject instead) + +```javascript +const result = R.pipe( + [1, 2, 3], + R.filterMap(x => x > 1 ? x : null) +) +// => [2, 3] +``` + +Try this R.filterMap example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +filterMap( + fn: (value: T[number], index: number) => U, +): (data: T) => Mapped>; +filterMap( + fn: (value: T[number]) => U, +): (data: T) => Mapped>; +``` + +
+ +
+ +R.filterMap source + +```javascript +import {mapFn} from './map.js' + +export function filterMap(fn) { + return list => mapFn(fn, list).filter(Boolean) +} +``` + +
+ +
+ +Tests + +```javascript +import { filterMap } from './filterMap.js' + +const double = x => x > 1 ? x * 2 : null + +it('happy', () => { + expect(filterMap(double)([1, 2, 3])).toEqual([4, 6]) +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { filterMap, pipe } from 'rambda' + +const list = [1, 2, 3] + +it('R.filterMap - within pipe', () => { + const result = pipe( + list, + x => x, + filterMap(x => { + x // $ExpectType number + return Math.random() > 0.5 ? String(x) : null + }), + filterMap(x => { + x // $ExpectType string + return Math.random() > 0.5 ? Number(x) : '' + }), + ) + result // $ExpectType number[] +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#filterMap) + ### filterObject ```typescript @@ -5151,9 +5300,6 @@ indexBy( indexBy( property: K ): (list: T[]) => Record; - -// API_MARKER_END -// ============================================ ``` @@ -5327,7 +5473,9 @@ describe('R.indexOf', () => { ```typescript -init(input: T): T extends readonly [...infer U, any] ? U : [...T] +init(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [...infer U, any] ? U : T : T extends string ? string : never ``` It returns all but the last element of list or string `input`. @@ -5347,8 +5495,9 @@ const result = [ All TypeScript definitions ```typescript -init(input: T): T extends readonly [...infer U, any] ? U : [...T]; -init(input: string): string; +init(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [...infer U, any] ? U : T : T extends string ? string : never; ``` @@ -5401,7 +5550,7 @@ test('with string', () => { TypeScript test ```typescript -import { init } from 'rambda' +import { map, pipe, init } from 'rambda' describe('R.init', () => { it('with string', () => { @@ -5409,13 +5558,32 @@ describe('R.init', () => { result // $ExpectType string }) - it('with list - one type', () => { - const result = init([1, 2, 3]) - - result // $ExpectType number[] + it('with list - using const on short array', () => { + const result = pipe( + [1] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [number, number] }) it('with list - mixed types', () => { - const result = init([1, 2, 3, 'foo', 'bar']) + const result = init(['foo', 'bar', 1, 2, 3]) result // $ExpectType (string | number)[] }) @@ -6126,16 +6294,14 @@ It returns the result of looping through `iterable` with `fn`. > :boom: This function doesn't work with objects (use R.mapObject instead) ```javascript -const fn = x => x * 2 - -const iterable = [1, 2] -const obj = {a: 1, b: 2} - -const result = R.map(fn)(iterable), +const result = R.pipe( + [1, 2], + R.map(x => x * 2) +) // => [2, 4] ``` -Try this R.map example in Rambda REPL +Try this R.map example in Rambda REPL
@@ -6201,7 +6367,7 @@ import { map, pipe } from 'rambda' const list = [1, 2, 3] -it('R.map - within pipe', () => { +it('R.map', () => { const result = pipe( list, x => x, @@ -6213,6 +6379,19 @@ it('R.map - within pipe', () => { result // $ExpectType string[] }) +it('R.map - index in functor', () => { + const result = pipe( + list, + x => x, + map((x, i) => { + x // $ExpectType number + i // $ExpectType number + return String(x) + }), + ) + result // $ExpectType string[] +}) + it('R.map - without pipe', () => { map(x => { x // $ExpectType unknown @@ -6380,6 +6559,165 @@ it('R.mapAsync', async () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#mapAsync) +### mapChain + +```typescript + +mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped +``` + +Chained 2 or 3 `R.map` transformations as one. + +```javascript +const result = R.pipe( + [1, 2], + R.mapChain( + x => x * 2, + x => [x, x > 3], + ) +) +// => [[2, false], [4, true]] +``` + +Try this R.mapChain example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +... +... +``` + +
+ +
+ +R.mapChain source + +```javascript +import { mapFn } from './map.js'; + +export function mapChain(...fns) { + return list => { + let result = list.slice() + fns.forEach((fn) => { + result = mapFn(fn, result) + }) + return result + } +} +``` + +
+ +
+ +Tests + +```javascript +import { mapChain } from './mapChain.js' + +const double = x => x * 2 + +it('happy', () => { + expect(mapChain(double, double, double)([1, 2, 3])).toEqual([8, 16, 24]) +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { mapChain, pipe } from 'rambda' + +const list = [1, 2, 3] + +it('R.mapChain', () => { + const result = pipe( + list, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + x => { + x // $ExpectType string + return x !== 'foo' + }, + ), + ) + result // $ExpectType boolean[] +}) + +it('R.mapChain - with index', () => { + const result = pipe( + list, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + (x, i) => { + i // $ExpectType number + x // $ExpectType string + return x !== 'foo' + }, + ), + ) + result // $ExpectType boolean[] +}) + +it('R.mapChain - 3 functions', () => { + const result = pipe( + list, + x => x, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + x => { + x // $ExpectType string + return x !== 'foo' + }, + x => { + x // $ExpectType boolean + return x ? 'foo' : 'bar' + }, + ), + ) + result // $ExpectType ("foo" | "bar")[] +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#mapChain) + ### mapKeys ```typescript @@ -6435,7 +6773,7 @@ export function mapKeys(fn) { Tests ```javascript -import { mapKeys } from "./mapKeys.js" +import { mapKeys } from './mapKeys.js' test('happy', () => { const result = mapKeys((prop, x) => `${ prop }-${x}`)({a:1, b: 2 }) @@ -7248,11 +7586,130 @@ export function mergeTypes(x) { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#mergeTypes) -### minBy +### middle ```typescript -minBy(compareFn: (input: T) => Ord, x: T): (y: T) => T +middle(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : + T extends [any, ...infer U, any] ? U : T : T extends string ? string : never +``` + +It returns all but the first and last element of `input`. + +```javascript +const result = [ + R.middle([1, 2, 3, 4]), + R.middle('bar') +] +// => [[2, 3], 'a'] +``` + +Try this R.middle example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +middle(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : + T extends [any, ...infer U, any] ? U : T : T extends string ? string : never; +``` + +
+ +
+ +R.middle source + +```javascript +import { init } from './init.js' +import { tail } from './tail.js' + +export function middle(listOrString) { + return tail(init(listOrString)) +} +``` + +
+ +
+ +Tests + +```javascript +import { middle } from './middle' + +test('middle', () => { + expect(middle([1, 2, 3])).toEqual([2]) + expect(middle([1, 2])).toEqual([]) + expect(middle([1])).toEqual([]) + expect(middle([])).toEqual([]) + + expect(middle('abc')).toBe('b') + expect(middle('ab')).toBe('') + expect(middle('a')).toBe('') + expect(middle('')).toBe('') +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { map, middle, pipe } from 'rambda' + +describe('R.middle', () => { + it('with string', () => { + const result = middle('foo') + + result // $ExpectType string + }) + it('with list - using const on short array', () => { + const result = pipe( + [1, 2] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3, 4] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [number, number] + }) + it('with list - mixed types', () => { + const result = middle(['foo', 'bar', 1, 2, 3]) + + result // $ExpectType (string | number)[] + }) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#middle) + +### minBy + +```typescript + +minBy(compareFn: (input: T) => Ord, x: T): (y: T) => T ``` It returns the lesser value between `x` and `y` according to `compareFn` function. @@ -9434,7 +9891,7 @@ test('with undefined', () => { TypeScript test ```typescript -import { pipe, pluck } from "rambda"; +import { pipe, pluck } from 'rambda'; it("R.pluck", () => { const input = [ @@ -9861,6 +10318,56 @@ describe('R.propSatisfies', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#propSatisfies) +### random + +```typescript + +random(minInclusive: number, maxInclusive: number): number +``` + +It returns a random number between `min` inclusive and `max` inclusive. + +
+ +All TypeScript definitions + +```typescript +random(minInclusive: number, maxInclusive: number): number; +``` + +
+ +
+ +R.random source + +```javascript +export function random(min, max){ + return Math.floor(Math.random() * (max - min + 1)) + min +} +``` + +
+ +
+ +Tests + +```javascript +import { random } from './random.js' +import { range } from './range.js' +import { uniq } from './uniq.js' + +test('happy', () => { + const result = uniq(range(100).map(() => random(0, 3))).sort() + expect(result).toEqual([0,1,2,3]) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#random) + ### range ```typescript @@ -10207,6 +10714,17 @@ describe('R.reject with array', () => { }), ) result // $ExpectType number[] + }) + it('with index', () => { + const result = pipe( + list, + reject((x: number, i: number) => { + x // $ExpectType number + i // $ExpectType number + return x > 1 + }), + ) + result // $ExpectType number[] }) it('narrowing type', () => { interface Foo { @@ -10565,15 +11083,6 @@ shuffle(list: T[]): T[] It returns a randomized copy of array. -```javascript -const result = R.shuffle( - [1, 2, 3] -) -// => [3, 1, 2] or [2, 3, 1] or ... -``` - -Try this R.shuffle example in Rambda REPL -
All TypeScript definitions @@ -10901,7 +11410,7 @@ sortByDescending(sortFn: (x: T) => Ord): (list: T[]) => T[]; R.sortByDescending source ```javascript -import { sortByFn } from "./sortBy.js"; +import { sortByFn } from './sortBy.js'; export function sortByDescending(sortFn) { return list => sortByFn(sortFn, list, true) @@ -11600,6 +12109,300 @@ describe('R.splitEvery', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#splitEvery) +### sum + +```typescript + +sum(list: number[]): number +``` + +```javascript +const result = R.sum( + [1,2,3] +) +// => 6 +``` + +Try this R.sum example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +sum(list: number[]): number; +``` + +
+ +
+ +R.sum source + +```javascript +export function sum(list){ + return list.reduce((acc, cur) => acc + cur, 0) +} +``` + +
+ +
+ +Tests + +```javascript +import { sum } from './sum.js' + +test('happy', () => { + expect(sum([1,2,3])).toEqual(6) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#sum) + +### switcher + +```typescript + +switcher(valueToMatch: T): Switchem +``` + +```javascript +const list = [1, 2, 3] + +const result = switcher(list.length) + .is(x => x < 2, 4) + .is(x => x < 4, 6) + .default(7) +// => 6 +``` + +Try this R.switcher example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +switcher(valueToMatch: T): Switchem; +switcher(valueToMatch: T): Switchem2; + +// API_MARKER_END +// ============================================ +``` + +
+ +
+ +R.switcher source + +```javascript +import { equals } from './equals.js' + +const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined + +const getMatchingKeyValuePair = ( + cases, testValue, defaultValue +) => { + let iterationValue + + for (let index = 0; index < cases.length; index++){ + iterationValue = cases[ index ].test(testValue) + + if (iterationValue !== NO_MATCH_FOUND){ + return iterationValue + } + } + + return defaultValue +} + +const isEqual = (testValue, matchValue) => { + const willReturn = + typeof testValue === 'function' ? + testValue(matchValue) : + equals(testValue)(matchValue) + + return willReturn +} + +const is = (testValue, matchResult = true) => ({ + key : testValue, + test : matchValue => + isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND, +}) + +class Switchem{ + constructor( + defaultValue, cases, willMatch + ){ + if (cases === undefined && willMatch === undefined){ + this.cases = [] + this.defaultValue = undefined + this.willMatch = defaultValue + } else { + this.cases = cases + this.defaultValue = defaultValue + this.willMatch = willMatch + } + + return this + } + + default(defaultValue){ + const holder = new Switchem( + defaultValue, this.cases, this.willMatch + ) + + return holder.match(this.willMatch) + } + + is(testValue, matchResult){ + return new Switchem( + this.defaultValue, + [ ...this.cases, is(testValue, matchResult) ], + this.willMatch + ) + } + + match(matchValue){ + return getMatchingKeyValuePair( + this.cases, matchValue, this.defaultValue + ) + } +} + +export function switcher(input){ + return new Switchem(input) +} +``` + +
+ +
+ +Tests + +```javascript +import { switcher } from './switcher.js' +import { tap } from './tap.js' + +test('with undefined', () => { + const result = switcher(undefined) + .is(x => x === 0, '0') + .is(x => x === undefined, 'UNDEFINED') + .default('3') + + expect(result).toBe('UNDEFINED') +}) + +test('happy', () => { + const a = true + const b = false + const result = switcher([ a, b ]) + .is([ false, false ], '0') + .is([ false, true ], '1') + .is([ true, true ], '2') + .default('3') + + expect(result).toBe('3') +}) + +test('can compare objects', () => { + const result = switcher({ a : 1 }) + .is({ a : 1 }, 'it is object') + .is('baz', 'it is baz') + .default('it is default') + + expect(result).toBe('it is object') +}) + +test('options are mixture of functions and values - input match function', () => { + const fn = switcher('foo').is('bar', 1) + .is('foo', x => x + 1) + .default(1000) + + expect(fn(2)).toBe(3) +}) + +test('options are mixture of functions and values - input match value', () => { + const result = switcher('bar').is('bar', 1) + .is('foo', x => x + 1) + .default(1000) + + expect(result).toBe(1) +}) + +test('return function if all options are functions', () => { + const fn = switcher('foo') + .is('bar', tap) + .is('foo', x => x + 1) + .default(9) + + expect(fn(2)).toBe(3) +}) + +const switchFn = input => + switcher(input) + .is(x => x.length && x.length === 7, 'has length of 7') + .is('baz', 'it is baz') + .default('it is default') + +test('works with function as condition', () => { + expect(switchFn([ 0, 1, 2, 3, 4, 5, 6 ])).toBe('has length of 7') +}) + +test('works with string as condition', () => { + expect(switchFn('baz')).toBe('it is baz') +}) + +test('fallback to default input when no matches', () => { + expect(switchFn(1)).toBe('it is default') +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { switcher } from 'rambda' + +describe('R.switcher', () => { + it('no transformation', () => { + const list = [1, 2, 3] + + const result = switcher(list.length) + .is(x => x < 2, 4) + .is(x => x < 4, 6) + .default(7) + + result // $ExpectType number + }) + it('with transformation', () => { + const list = [1, 2, 3] + type Stage = 'firstStage' | 'secondStage' | 'thirdStage' + + const result = switcher(list.length) + .is(x => x < 2, 'firstStage') + .is(x => x < 4, 'secondStage') + .default('thirdStage') + + result // $ExpectType Stage + }) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#switcher) + ### symmetricDifference ```typescript @@ -11703,7 +12506,9 @@ describe('R.symmetricDifference', () => { ```typescript -tail(input: T): T extends [any, ...infer U] ? U : [...T] +tail(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [any, ...infer U] ? U : T : T extends string ? string : never ``` It returns all but the first element of `input`. @@ -11723,8 +12528,9 @@ const result = [ All TypeScript definitions ```typescript -tail(input: T): T extends [any, ...infer U] ? U : [...T]; -tail(input: string): string; +tail(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [any, ...infer U] ? U : T : T extends string ? string : never; ```
@@ -11770,7 +12576,7 @@ test('tail', () => { TypeScript test ```typescript -import { tail } from 'rambda' +import { map, pipe, tail } from 'rambda' describe('R.tail', () => { it('with string', () => { @@ -11778,10 +12584,29 @@ describe('R.tail', () => { result // $ExpectType string }) - it('with list - one type', () => { - const result = tail([1, 2, 3]) - - result // $ExpectType number[] + it('with list - using const on short array', () => { + const result = pipe( + [1] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [number, number] }) it('with list - mixed types', () => { const result = tail(['foo', 'bar', 1, 2, 3]) @@ -13973,6 +14798,20 @@ describe('R.zipWith', () => { ## ❯ CHANGELOG +11.1.0 + +- Add `R.filterMap` - similar to Ruby `filter_map` + +- Add `R.mapChain` - when in `R.pipe` there are several `R.map` one after the other, then `R.mapChain` can be used instead. + +- Add `R.middle` - equal to `R.init` + `R.tail` + +- Add `R.random`, `R.shuffle`, `R.switcher`, `R.sum`, `R.delay` - imported from `Rambda` + +- Add index to `R.filter`/`R.reject` predicate signiture + +- Improve typing of `R.init`, `R.tail` + 11.0.1 - Add missing JS change for `R.includes` and `R.excludes` methods in `11.0.0` release. diff --git a/dist/rambda.cjs b/dist/rambda.cjs index 9e439af6a..23ee0a762 100644 --- a/dist/rambda.cjs +++ b/dist/rambda.cjs @@ -249,6 +249,16 @@ function defaultTo(defaultArgument) { return input => isFalsy(input) ? defaultArgument : input } +const RAMBDA_DELAY = 'RAMBDA_DELAY'; + +function delay(ms) { + return new Promise(resolve => { + setTimeout(() => { + resolve(RAMBDA_DELAY); + }, ms); + }) +} + function descend(getFunction) { return (a, b) => { const aValue = getFunction(a); @@ -508,7 +518,7 @@ function difference(listA) { ]) } -function drop(howManyToDrop, ) { +function drop(howManyToDrop) { return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) } @@ -677,6 +687,10 @@ function filterAsync(predicate) { } } +function filterMap(fn) { + return list => mapFn(fn, list).filter(Boolean) +} + function filterObject(predicate) { return obj => { const willReturn = {}; @@ -1034,6 +1048,16 @@ function mapAsync(fn) { } } +function mapChain(...fns) { + return list => { + let result = list.slice(); + fns.forEach((fn) => { + result = mapFn(fn, result); + }); + return result + } +} + function mapKeys(fn) { return obj => { const willReturn = {}; @@ -1103,6 +1127,14 @@ function mergeTypes(x) { return x } +function tail(listOrString) { + return drop(1)(listOrString) +} + +function middle(listOrString) { + return tail(init(listOrString)) +} + function minBy(compareFn, x) { return y => (compareFn(y) < compareFn(x) ? y : x) } @@ -1520,6 +1552,10 @@ function propSatisfies(predicate, property) { return obj => predicate(obj[property]) } +function random(min, max){ + return Math.floor(Math.random() * (max - min + 1)) + min +} + function range(a, b) { const start = b === undefined ? 0 : a; const end = b === undefined ? a : b; @@ -1669,6 +1705,87 @@ function splitEvery(sliceLength) { } } +function sum(list){ + return list.reduce((acc, cur) => acc + cur, 0) +} + +const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined; + +const getMatchingKeyValuePair = ( + cases, testValue, defaultValue +) => { + let iterationValue; + + for (let index = 0; index < cases.length; index++){ + iterationValue = cases[ index ].test(testValue); + + if (iterationValue !== NO_MATCH_FOUND){ + return iterationValue + } + } + + return defaultValue +}; + +const isEqual = (testValue, matchValue) => { + const willReturn = + typeof testValue === 'function' ? + testValue(matchValue) : + equals(testValue)(matchValue); + + return willReturn +}; + +const is = (testValue, matchResult = true) => ({ + key : testValue, + test : matchValue => + isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND, +}); + +class Switchem{ + constructor( + defaultValue, cases, willMatch + ){ + if (cases === undefined && willMatch === undefined){ + this.cases = []; + this.defaultValue = undefined; + this.willMatch = defaultValue; + } else { + this.cases = cases; + this.defaultValue = defaultValue; + this.willMatch = willMatch; + } + + return this + } + + default(defaultValue){ + const holder = new Switchem( + defaultValue, this.cases, this.willMatch + ); + + return holder.match(this.willMatch) + } + + is(testValue, matchResult){ + return new Switchem( + this.defaultValue, + [ ...this.cases, is(testValue, matchResult) ], + this.willMatch + ) + } + + match(matchValue){ + return getMatchingKeyValuePair( + this.cases, matchValue, this.defaultValue + ) + } +} + +function switcher(input){ + return new Switchem(input) +} + function symmetricDifference(listA) { return listB => [ ...filter(excludes(listB))(listA), @@ -1676,10 +1793,6 @@ function symmetricDifference(listA) { ] } -function tail(listOrString) { - return drop(1)(listOrString) -} - function take(numberOfItems) { return input => { if (numberOfItems < 0) { @@ -1890,6 +2003,7 @@ function zipWith(fn, x) { ) } +exports.RAMBDA_DELAY = RAMBDA_DELAY; exports._arity = _arity; exports._includes = _includes; exports._indexOf = _indexOf; @@ -1913,6 +2027,7 @@ exports.countBy = countBy; exports.createCompareFunction = createCompareFunction; exports.createObjectFromKeys = createObjectFromKeys; exports.defaultTo = defaultTo; +exports.delay = delay; exports.descend = descend; exports.difference = difference; exports.drop = drop; @@ -1929,6 +2044,7 @@ exports.excludes = excludes; exports.exists = exists; exports.filter = filter; exports.filterAsync = filterAsync; +exports.filterMap = filterMap; exports.filterObject = filterObject; exports.find = find; exports.findIndex = findIndex; @@ -1955,6 +2071,7 @@ exports.last = last; exports.lastIndexOf = lastIndexOf; exports.map = map; exports.mapAsync = mapAsync; +exports.mapChain = mapChain; exports.mapFn = mapFn; exports.mapKeys = mapKeys; exports.mapObject = mapObject; @@ -1965,6 +2082,7 @@ exports.match = match; exports.maxBy = maxBy; exports.merge = merge; exports.mergeTypes = mergeTypes; +exports.middle = middle; exports.minBy = minBy; exports.modifyItemAtIndex = modifyItemAtIndex; exports.modifyPath = modifyPath; @@ -1987,6 +2105,7 @@ exports.prop = prop; exports.propEq = propEq; exports.propOr = propOr; exports.propSatisfies = propSatisfies; +exports.random = random; exports.range = range; exports.rangeDescending = rangeDescending; exports.reduce = reduce; @@ -2005,6 +2124,8 @@ exports.sortObject = sortObject; exports.sortWith = sortWith; exports.split = split; exports.splitEvery = splitEvery; +exports.sum = sum; +exports.switcher = switcher; exports.symmetricDifference = symmetricDifference; exports.tail = tail; exports.take = take; diff --git a/dist/rambda.js b/dist/rambda.js index c87c3ebb9..3019bcfe2 100644 --- a/dist/rambda.js +++ b/dist/rambda.js @@ -247,6 +247,16 @@ function defaultTo(defaultArgument) { return input => isFalsy(input) ? defaultArgument : input } +const RAMBDA_DELAY = 'RAMBDA_DELAY'; + +function delay(ms) { + return new Promise(resolve => { + setTimeout(() => { + resolve(RAMBDA_DELAY); + }, ms); + }) +} + function descend(getFunction) { return (a, b) => { const aValue = getFunction(a); @@ -506,7 +516,7 @@ function difference(listA) { ]) } -function drop(howManyToDrop, ) { +function drop(howManyToDrop) { return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) } @@ -675,6 +685,10 @@ function filterAsync(predicate) { } } +function filterMap(fn) { + return list => mapFn(fn, list).filter(Boolean) +} + function filterObject(predicate) { return obj => { const willReturn = {}; @@ -1032,6 +1046,16 @@ function mapAsync(fn) { } } +function mapChain(...fns) { + return list => { + let result = list.slice(); + fns.forEach((fn) => { + result = mapFn(fn, result); + }); + return result + } +} + function mapKeys(fn) { return obj => { const willReturn = {}; @@ -1101,6 +1125,14 @@ function mergeTypes(x) { return x } +function tail(listOrString) { + return drop(1)(listOrString) +} + +function middle(listOrString) { + return tail(init(listOrString)) +} + function minBy(compareFn, x) { return y => (compareFn(y) < compareFn(x) ? y : x) } @@ -1518,6 +1550,10 @@ function propSatisfies(predicate, property) { return obj => predicate(obj[property]) } +function random(min, max){ + return Math.floor(Math.random() * (max - min + 1)) + min +} + function range(a, b) { const start = b === undefined ? 0 : a; const end = b === undefined ? a : b; @@ -1667,6 +1703,87 @@ function splitEvery(sliceLength) { } } +function sum(list){ + return list.reduce((acc, cur) => acc + cur, 0) +} + +const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined; + +const getMatchingKeyValuePair = ( + cases, testValue, defaultValue +) => { + let iterationValue; + + for (let index = 0; index < cases.length; index++){ + iterationValue = cases[ index ].test(testValue); + + if (iterationValue !== NO_MATCH_FOUND){ + return iterationValue + } + } + + return defaultValue +}; + +const isEqual = (testValue, matchValue) => { + const willReturn = + typeof testValue === 'function' ? + testValue(matchValue) : + equals(testValue)(matchValue); + + return willReturn +}; + +const is = (testValue, matchResult = true) => ({ + key : testValue, + test : matchValue => + isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND, +}); + +class Switchem{ + constructor( + defaultValue, cases, willMatch + ){ + if (cases === undefined && willMatch === undefined){ + this.cases = []; + this.defaultValue = undefined; + this.willMatch = defaultValue; + } else { + this.cases = cases; + this.defaultValue = defaultValue; + this.willMatch = willMatch; + } + + return this + } + + default(defaultValue){ + const holder = new Switchem( + defaultValue, this.cases, this.willMatch + ); + + return holder.match(this.willMatch) + } + + is(testValue, matchResult){ + return new Switchem( + this.defaultValue, + [ ...this.cases, is(testValue, matchResult) ], + this.willMatch + ) + } + + match(matchValue){ + return getMatchingKeyValuePair( + this.cases, matchValue, this.defaultValue + ) + } +} + +function switcher(input){ + return new Switchem(input) +} + function symmetricDifference(listA) { return listB => [ ...filter(excludes(listB))(listA), @@ -1674,10 +1791,6 @@ function symmetricDifference(listA) { ] } -function tail(listOrString) { - return drop(1)(listOrString) -} - function take(numberOfItems) { return input => { if (numberOfItems < 0) { @@ -1888,4 +2001,4 @@ function zipWith(fn, x) { ) } -export { _arity, _includes, _indexOf, _lastIndexOf, addProp, addPropToObjects, all, allPass, any, anyPass, append, ascend, assertType, checkObjectWithSpec, compact, complement, concat, convertToType, count, countBy, createCompareFunction, createObjectFromKeys, defaultTo, descend, difference, drop, dropLast, dropLastWhile, dropWhile, duplicateBy, eqBy, eqProps, equals, equalsFn, evolve, excludes, exists, filter, filterAsync, filterObject, find, findIndex, findLast, findLastIndex, findNth, flatMap, flatten, flattenObject, flattenObjectHelper, groupBy, groupByFallback, head, includes, indexBy, indexOf, init, interpolate, intersection, intersectionWith, intersperse, join, last, lastIndexOf, map, mapAsync, mapFn, mapKeys, mapObject, mapObjectAsync, mapParallelAsync, mapPropObject, match, maxBy, merge, mergeTypes, minBy, modifyItemAtIndex, modifyPath, modifyProp, none, objOf, objectIncludes, omit, partition, partitionObject, path, pathSatisfies, permutations, pick, pipe, pipeAsync, pluck, prepend, prop, propEq, propOr, propSatisfies, range, rangeDescending, reduce, reject, rejectObject, replace, replaceAll, shuffle, sort, sortBy, sortByDescending, sortByFn, sortByPath, sortByPathDescending, sortObject, sortWith, split, splitEvery, symmetricDifference, tail, take, takeLast, takeLastWhile, takeWhile, tap, test, transformFlatObject, tryCatch, type, union, unionWith, uniq, uniqBy, uniqWith, unless, unwind, update, when, zip, zipWith }; +export { RAMBDA_DELAY, _arity, _includes, _indexOf, _lastIndexOf, addProp, addPropToObjects, all, allPass, any, anyPass, append, ascend, assertType, checkObjectWithSpec, compact, complement, concat, convertToType, count, countBy, createCompareFunction, createObjectFromKeys, defaultTo, delay, descend, difference, drop, dropLast, dropLastWhile, dropWhile, duplicateBy, eqBy, eqProps, equals, equalsFn, evolve, excludes, exists, filter, filterAsync, filterMap, filterObject, find, findIndex, findLast, findLastIndex, findNth, flatMap, flatten, flattenObject, flattenObjectHelper, groupBy, groupByFallback, head, includes, indexBy, indexOf, init, interpolate, intersection, intersectionWith, intersperse, join, last, lastIndexOf, map, mapAsync, mapChain, mapFn, mapKeys, mapObject, mapObjectAsync, mapParallelAsync, mapPropObject, match, maxBy, merge, mergeTypes, middle, minBy, modifyItemAtIndex, modifyPath, modifyProp, none, objOf, objectIncludes, omit, partition, partitionObject, path, pathSatisfies, permutations, pick, pipe, pipeAsync, pluck, prepend, prop, propEq, propOr, propSatisfies, random, range, rangeDescending, reduce, reject, rejectObject, replace, replaceAll, shuffle, sort, sortBy, sortByDescending, sortByFn, sortByPath, sortByPathDescending, sortObject, sortWith, split, splitEvery, sum, switcher, symmetricDifference, tail, take, takeLast, takeLastWhile, takeWhile, tap, test, transformFlatObject, tryCatch, type, union, unionWith, uniq, uniqBy, uniqWith, unless, unwind, update, when, zip, zipWith }; diff --git a/dist/rambda.umd.js b/dist/rambda.umd.js index 8b9326144..23636fe1a 100644 --- a/dist/rambda.umd.js +++ b/dist/rambda.umd.js @@ -253,6 +253,16 @@ return input => isFalsy(input) ? defaultArgument : input } + const RAMBDA_DELAY = 'RAMBDA_DELAY'; + + function delay(ms) { + return new Promise(resolve => { + setTimeout(() => { + resolve(RAMBDA_DELAY); + }, ms); + }) + } + function descend(getFunction) { return (a, b) => { const aValue = getFunction(a); @@ -512,7 +522,7 @@ ]) } - function drop(howManyToDrop, ) { + function drop(howManyToDrop) { return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) } @@ -681,6 +691,10 @@ } } + function filterMap(fn) { + return list => mapFn(fn, list).filter(Boolean) + } + function filterObject(predicate) { return obj => { const willReturn = {}; @@ -1038,6 +1052,16 @@ } } + function mapChain(...fns) { + return list => { + let result = list.slice(); + fns.forEach((fn) => { + result = mapFn(fn, result); + }); + return result + } + } + function mapKeys(fn) { return obj => { const willReturn = {}; @@ -1107,6 +1131,14 @@ return x } + function tail(listOrString) { + return drop(1)(listOrString) + } + + function middle(listOrString) { + return tail(init(listOrString)) + } + function minBy(compareFn, x) { return y => (compareFn(y) < compareFn(x) ? y : x) } @@ -1524,6 +1556,10 @@ return obj => predicate(obj[property]) } + function random(min, max){ + return Math.floor(Math.random() * (max - min + 1)) + min + } + function range(a, b) { const start = b === undefined ? 0 : a; const end = b === undefined ? a : b; @@ -1673,6 +1709,87 @@ } } + function sum(list){ + return list.reduce((acc, cur) => acc + cur, 0) + } + + const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined; + + const getMatchingKeyValuePair = ( + cases, testValue, defaultValue + ) => { + let iterationValue; + + for (let index = 0; index < cases.length; index++){ + iterationValue = cases[ index ].test(testValue); + + if (iterationValue !== NO_MATCH_FOUND){ + return iterationValue + } + } + + return defaultValue + }; + + const isEqual = (testValue, matchValue) => { + const willReturn = + typeof testValue === 'function' ? + testValue(matchValue) : + equals(testValue)(matchValue); + + return willReturn + }; + + const is = (testValue, matchResult = true) => ({ + key : testValue, + test : matchValue => + isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND, + }); + + class Switchem{ + constructor( + defaultValue, cases, willMatch + ){ + if (cases === undefined && willMatch === undefined){ + this.cases = []; + this.defaultValue = undefined; + this.willMatch = defaultValue; + } else { + this.cases = cases; + this.defaultValue = defaultValue; + this.willMatch = willMatch; + } + + return this + } + + default(defaultValue){ + const holder = new Switchem( + defaultValue, this.cases, this.willMatch + ); + + return holder.match(this.willMatch) + } + + is(testValue, matchResult){ + return new Switchem( + this.defaultValue, + [ ...this.cases, is(testValue, matchResult) ], + this.willMatch + ) + } + + match(matchValue){ + return getMatchingKeyValuePair( + this.cases, matchValue, this.defaultValue + ) + } + } + + function switcher(input){ + return new Switchem(input) + } + function symmetricDifference(listA) { return listB => [ ...filter(excludes(listB))(listA), @@ -1680,10 +1797,6 @@ ] } - function tail(listOrString) { - return drop(1)(listOrString) - } - function take(numberOfItems) { return input => { if (numberOfItems < 0) { @@ -1894,6 +2007,7 @@ ) } + exports.RAMBDA_DELAY = RAMBDA_DELAY; exports._arity = _arity; exports._includes = _includes; exports._indexOf = _indexOf; @@ -1917,6 +2031,7 @@ exports.createCompareFunction = createCompareFunction; exports.createObjectFromKeys = createObjectFromKeys; exports.defaultTo = defaultTo; + exports.delay = delay; exports.descend = descend; exports.difference = difference; exports.drop = drop; @@ -1933,6 +2048,7 @@ exports.exists = exists; exports.filter = filter; exports.filterAsync = filterAsync; + exports.filterMap = filterMap; exports.filterObject = filterObject; exports.find = find; exports.findIndex = findIndex; @@ -1959,6 +2075,7 @@ exports.lastIndexOf = lastIndexOf; exports.map = map; exports.mapAsync = mapAsync; + exports.mapChain = mapChain; exports.mapFn = mapFn; exports.mapKeys = mapKeys; exports.mapObject = mapObject; @@ -1969,6 +2086,7 @@ exports.maxBy = maxBy; exports.merge = merge; exports.mergeTypes = mergeTypes; + exports.middle = middle; exports.minBy = minBy; exports.modifyItemAtIndex = modifyItemAtIndex; exports.modifyPath = modifyPath; @@ -1991,6 +2109,7 @@ exports.propEq = propEq; exports.propOr = propOr; exports.propSatisfies = propSatisfies; + exports.random = random; exports.range = range; exports.rangeDescending = rangeDescending; exports.reduce = reduce; @@ -2009,6 +2128,8 @@ exports.sortWith = sortWith; exports.split = split; exports.splitEvery = splitEvery; + exports.sum = sum; + exports.switcher = switcher; exports.symmetricDifference = symmetricDifference; exports.tail = tail; exports.take = take; diff --git a/docs/README.md b/docs/README.md index efed3f30d..d2af17fd0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -215,7 +215,7 @@ export function addProp(key, value) { Tests ```javascript -import { addProp } from "./addProp.js" +import { addProp } from './addProp.js' test('happy', () => { const result = addProp('a', 1)({ b: 2 }) @@ -325,8 +325,8 @@ export function addPropToObjects ( Tests ```javascript -import { pipe } from "./pipe.js" -import { addPropToObjects } from "./addPropToObjects.js" +import { pipe } from './pipe.js' +import { addPropToObjects } from './addPropToObjects.js' test('R.addPropToObjects', () => { let result = pipe( @@ -1941,6 +1941,45 @@ describe('R.defaultTo', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#defaultTo) +### delay + +```typescript + +delay(ms: number): Promise<'RAMBDA_DELAY'> +``` + +`setTimeout` as a promise that resolves to `RAMBDA_DELAY` string after `ms` milliseconds. + +
+ +All TypeScript definitions + +```typescript +delay(ms: number): Promise<'RAMBDA_DELAY'>; +``` + +
+ +
+ +R.delay source + +```javascript +export const RAMBDA_DELAY = 'RAMBDA_DELAY' + +export function delay(ms) { + return new Promise(resolve => { + setTimeout(() => { + resolve(RAMBDA_DELAY) + }, ms) + }) +} +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#delay) + ### descend ```typescript @@ -2120,7 +2159,7 @@ drop(howMany: number): (list: T[]) => T[]; R.drop source ```javascript -export function drop(howManyToDrop, ) { +export function drop(howManyToDrop) { return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) } ``` @@ -3529,7 +3568,7 @@ filter( predicate: BooleanConstructor, ): (list: T[]) => ExcludeFalsy[]; filter( - predicate: (value: T) => boolean, + predicate: (value: T, index: number) => boolean, ): (list: T[]) => T[]; ... ... @@ -3597,14 +3636,26 @@ const list = [1, 2, 3] describe('R.filter with array', () => { it('within pipe', () => { - const _result = pipe( + const result = pipe( list, filter(x => { x // $ExpectType number return x > 1 }), ) - _result // $ExpectType number[] + result // $ExpectType number[] + }) + + it('with index', () => { + const result = pipe( + list, + filter((x: number, i: number) => { + x // $ExpectType number + i // $ExpectType number + return x > 1 + }), + ) + result // $ExpectType number[] }) it('complex example', () => { @@ -3643,8 +3694,8 @@ describe('R.filter with array', () => { const filterBar = (x: T): x is Bar => { return typeof (x as Bar).b === 'string' } - const _result = pipe(testList, filter(filterBar)) - _result // $ExpectType Bar[] + const result = pipe(testList, filter(filterBar)) + result // $ExpectType Bar[] }) it('narrowing type - readonly', () => { @@ -3659,14 +3710,14 @@ describe('R.filter with array', () => { const filterBar = (x: T): x is Bar => { return typeof (x as Bar).b === 'string' } - const _result = pipe(testList, filter(filterBar)) - _result // $ExpectType Bar[] + const result = pipe(testList, filter(filterBar)) + result // $ExpectType Bar[] }) it('filtering NonNullable - list of objects', () => { const testList = [{ a: 1 }, { a: 2 }, false, { a: 3 }] - const _result = pipe(testList, filter(Boolean)) - _result // $ExpectType { a: number; }[] + const result = pipe(testList, filter(Boolean)) + result // $ExpectType { a: number; }[] }) it('filtering NonNullable - readonly', () => { @@ -3778,6 +3829,104 @@ describe('R.filter with array', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#filterAsync) +### filterMap + +```typescript + +filterMap( + fn: (value: T[number], index: number) => U, +): (data: T) => Mapped> +``` + +Same as `R.map` but it filters out `null/undefined` if returned from functor functions. + +> :boom: This function doesn't work with objects (use R.mapObject instead) + +```javascript +const result = R.pipe( + [1, 2, 3], + R.filterMap(x => x > 1 ? x : null) +) +// => [2, 3] +``` + +Try this R.filterMap example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +filterMap( + fn: (value: T[number], index: number) => U, +): (data: T) => Mapped>; +filterMap( + fn: (value: T[number]) => U, +): (data: T) => Mapped>; +``` + +
+ +
+ +R.filterMap source + +```javascript +import {mapFn} from './map.js' + +export function filterMap(fn) { + return list => mapFn(fn, list).filter(Boolean) +} +``` + +
+ +
+ +Tests + +```javascript +import { filterMap } from './filterMap.js' + +const double = x => x > 1 ? x * 2 : null + +it('happy', () => { + expect(filterMap(double)([1, 2, 3])).toEqual([4, 6]) +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { filterMap, pipe } from 'rambda' + +const list = [1, 2, 3] + +it('R.filterMap - within pipe', () => { + const result = pipe( + list, + x => x, + filterMap(x => { + x // $ExpectType number + return Math.random() > 0.5 ? String(x) : null + }), + filterMap(x => { + x // $ExpectType string + return Math.random() > 0.5 ? Number(x) : '' + }), + ) + result // $ExpectType number[] +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#filterMap) + ### filterObject ```typescript @@ -5151,9 +5300,6 @@ indexBy( indexBy( property: K ): (list: T[]) => Record; - -// API_MARKER_END -// ============================================ ```
@@ -5327,7 +5473,9 @@ describe('R.indexOf', () => { ```typescript -init(input: T): T extends readonly [...infer U, any] ? U : [...T] +init(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [...infer U, any] ? U : T : T extends string ? string : never ``` It returns all but the last element of list or string `input`. @@ -5347,8 +5495,9 @@ const result = [ All TypeScript definitions ```typescript -init(input: T): T extends readonly [...infer U, any] ? U : [...T]; -init(input: string): string; +init(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [...infer U, any] ? U : T : T extends string ? string : never; ``` @@ -5401,7 +5550,7 @@ test('with string', () => { TypeScript test ```typescript -import { init } from 'rambda' +import { map, pipe, init } from 'rambda' describe('R.init', () => { it('with string', () => { @@ -5409,13 +5558,32 @@ describe('R.init', () => { result // $ExpectType string }) - it('with list - one type', () => { - const result = init([1, 2, 3]) - - result // $ExpectType number[] + it('with list - using const on short array', () => { + const result = pipe( + [1] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [number, number] }) it('with list - mixed types', () => { - const result = init([1, 2, 3, 'foo', 'bar']) + const result = init(['foo', 'bar', 1, 2, 3]) result // $ExpectType (string | number)[] }) @@ -6126,16 +6294,14 @@ It returns the result of looping through `iterable` with `fn`. > :boom: This function doesn't work with objects (use R.mapObject instead) ```javascript -const fn = x => x * 2 - -const iterable = [1, 2] -const obj = {a: 1, b: 2} - -const result = R.map(fn)(iterable), +const result = R.pipe( + [1, 2], + R.map(x => x * 2) +) // => [2, 4] ``` -Try this R.map example in Rambda REPL +Try this R.map example in Rambda REPL
@@ -6201,7 +6367,7 @@ import { map, pipe } from 'rambda' const list = [1, 2, 3] -it('R.map - within pipe', () => { +it('R.map', () => { const result = pipe( list, x => x, @@ -6213,6 +6379,19 @@ it('R.map - within pipe', () => { result // $ExpectType string[] }) +it('R.map - index in functor', () => { + const result = pipe( + list, + x => x, + map((x, i) => { + x // $ExpectType number + i // $ExpectType number + return String(x) + }), + ) + result // $ExpectType string[] +}) + it('R.map - without pipe', () => { map(x => { x // $ExpectType unknown @@ -6380,6 +6559,165 @@ it('R.mapAsync', async () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#mapAsync) +### mapChain + +```typescript + +mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped +``` + +Chained 2 or 3 `R.map` transformations as one. + +```javascript +const result = R.pipe( + [1, 2], + R.mapChain( + x => x * 2, + x => [x, x > 3], + ) +) +// => [[2, false], [4, true]] +``` + +Try this R.mapChain example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +... +... +``` + +
+ +
+ +R.mapChain source + +```javascript +import { mapFn } from './map.js'; + +export function mapChain(...fns) { + return list => { + let result = list.slice() + fns.forEach((fn) => { + result = mapFn(fn, result) + }) + return result + } +} +``` + +
+ +
+ +Tests + +```javascript +import { mapChain } from './mapChain.js' + +const double = x => x * 2 + +it('happy', () => { + expect(mapChain(double, double, double)([1, 2, 3])).toEqual([8, 16, 24]) +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { mapChain, pipe } from 'rambda' + +const list = [1, 2, 3] + +it('R.mapChain', () => { + const result = pipe( + list, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + x => { + x // $ExpectType string + return x !== 'foo' + }, + ), + ) + result // $ExpectType boolean[] +}) + +it('R.mapChain - with index', () => { + const result = pipe( + list, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + (x, i) => { + i // $ExpectType number + x // $ExpectType string + return x !== 'foo' + }, + ), + ) + result // $ExpectType boolean[] +}) + +it('R.mapChain - 3 functions', () => { + const result = pipe( + list, + x => x, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + x => { + x // $ExpectType string + return x !== 'foo' + }, + x => { + x // $ExpectType boolean + return x ? 'foo' : 'bar' + }, + ), + ) + result // $ExpectType ("foo" | "bar")[] +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#mapChain) + ### mapKeys ```typescript @@ -6435,7 +6773,7 @@ export function mapKeys(fn) { Tests ```javascript -import { mapKeys } from "./mapKeys.js" +import { mapKeys } from './mapKeys.js' test('happy', () => { const result = mapKeys((prop, x) => `${ prop }-${x}`)({a:1, b: 2 }) @@ -7248,11 +7586,130 @@ export function mergeTypes(x) { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#mergeTypes) -### minBy +### middle ```typescript -minBy(compareFn: (input: T) => Ord, x: T): (y: T) => T +middle(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : + T extends [any, ...infer U, any] ? U : T : T extends string ? string : never +``` + +It returns all but the first and last element of `input`. + +```javascript +const result = [ + R.middle([1, 2, 3, 4]), + R.middle('bar') +] +// => [[2, 3], 'a'] +``` + +Try this R.middle example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +middle(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : + T extends [any, ...infer U, any] ? U : T : T extends string ? string : never; +``` + +
+ +
+ +R.middle source + +```javascript +import { init } from './init.js' +import { tail } from './tail.js' + +export function middle(listOrString) { + return tail(init(listOrString)) +} +``` + +
+ +
+ +Tests + +```javascript +import { middle } from './middle' + +test('middle', () => { + expect(middle([1, 2, 3])).toEqual([2]) + expect(middle([1, 2])).toEqual([]) + expect(middle([1])).toEqual([]) + expect(middle([])).toEqual([]) + + expect(middle('abc')).toBe('b') + expect(middle('ab')).toBe('') + expect(middle('a')).toBe('') + expect(middle('')).toBe('') +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { map, middle, pipe } from 'rambda' + +describe('R.middle', () => { + it('with string', () => { + const result = middle('foo') + + result // $ExpectType string + }) + it('with list - using const on short array', () => { + const result = pipe( + [1, 2] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3, 4] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [number, number] + }) + it('with list - mixed types', () => { + const result = middle(['foo', 'bar', 1, 2, 3]) + + result // $ExpectType (string | number)[] + }) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#middle) + +### minBy + +```typescript + +minBy(compareFn: (input: T) => Ord, x: T): (y: T) => T ``` It returns the lesser value between `x` and `y` according to `compareFn` function. @@ -9434,7 +9891,7 @@ test('with undefined', () => { TypeScript test ```typescript -import { pipe, pluck } from "rambda"; +import { pipe, pluck } from 'rambda'; it("R.pluck", () => { const input = [ @@ -9861,6 +10318,56 @@ describe('R.propSatisfies', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#propSatisfies) +### random + +```typescript + +random(minInclusive: number, maxInclusive: number): number +``` + +It returns a random number between `min` inclusive and `max` inclusive. + +
+ +All TypeScript definitions + +```typescript +random(minInclusive: number, maxInclusive: number): number; +``` + +
+ +
+ +R.random source + +```javascript +export function random(min, max){ + return Math.floor(Math.random() * (max - min + 1)) + min +} +``` + +
+ +
+ +Tests + +```javascript +import { random } from './random.js' +import { range } from './range.js' +import { uniq } from './uniq.js' + +test('happy', () => { + const result = uniq(range(100).map(() => random(0, 3))).sort() + expect(result).toEqual([0,1,2,3]) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#random) + ### range ```typescript @@ -10207,6 +10714,17 @@ describe('R.reject with array', () => { }), ) result // $ExpectType number[] + }) + it('with index', () => { + const result = pipe( + list, + reject((x: number, i: number) => { + x // $ExpectType number + i // $ExpectType number + return x > 1 + }), + ) + result // $ExpectType number[] }) it('narrowing type', () => { interface Foo { @@ -10565,15 +11083,6 @@ shuffle(list: T[]): T[] It returns a randomized copy of array. -```javascript -const result = R.shuffle( - [1, 2, 3] -) -// => [3, 1, 2] or [2, 3, 1] or ... -``` - -Try this R.shuffle example in Rambda REPL -
All TypeScript definitions @@ -10901,7 +11410,7 @@ sortByDescending(sortFn: (x: T) => Ord): (list: T[]) => T[]; R.sortByDescending source ```javascript -import { sortByFn } from "./sortBy.js"; +import { sortByFn } from './sortBy.js'; export function sortByDescending(sortFn) { return list => sortByFn(sortFn, list, true) @@ -11600,6 +12109,300 @@ describe('R.splitEvery', () => { [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#splitEvery) +### sum + +```typescript + +sum(list: number[]): number +``` + +```javascript +const result = R.sum( + [1,2,3] +) +// => 6 +``` + +Try this R.sum example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +sum(list: number[]): number; +``` + +
+ +
+ +R.sum source + +```javascript +export function sum(list){ + return list.reduce((acc, cur) => acc + cur, 0) +} +``` + +
+ +
+ +Tests + +```javascript +import { sum } from './sum.js' + +test('happy', () => { + expect(sum([1,2,3])).toEqual(6) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#sum) + +### switcher + +```typescript + +switcher(valueToMatch: T): Switchem +``` + +```javascript +const list = [1, 2, 3] + +const result = switcher(list.length) + .is(x => x < 2, 4) + .is(x => x < 4, 6) + .default(7) +// => 6 +``` + +Try this R.switcher example in Rambda REPL + +
+ +All TypeScript definitions + +```typescript +switcher(valueToMatch: T): Switchem; +switcher(valueToMatch: T): Switchem2; + +// API_MARKER_END +// ============================================ +``` + +
+ +
+ +R.switcher source + +```javascript +import { equals } from './equals.js' + +const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined + +const getMatchingKeyValuePair = ( + cases, testValue, defaultValue +) => { + let iterationValue + + for (let index = 0; index < cases.length; index++){ + iterationValue = cases[ index ].test(testValue) + + if (iterationValue !== NO_MATCH_FOUND){ + return iterationValue + } + } + + return defaultValue +} + +const isEqual = (testValue, matchValue) => { + const willReturn = + typeof testValue === 'function' ? + testValue(matchValue) : + equals(testValue)(matchValue) + + return willReturn +} + +const is = (testValue, matchResult = true) => ({ + key : testValue, + test : matchValue => + isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND, +}) + +class Switchem{ + constructor( + defaultValue, cases, willMatch + ){ + if (cases === undefined && willMatch === undefined){ + this.cases = [] + this.defaultValue = undefined + this.willMatch = defaultValue + } else { + this.cases = cases + this.defaultValue = defaultValue + this.willMatch = willMatch + } + + return this + } + + default(defaultValue){ + const holder = new Switchem( + defaultValue, this.cases, this.willMatch + ) + + return holder.match(this.willMatch) + } + + is(testValue, matchResult){ + return new Switchem( + this.defaultValue, + [ ...this.cases, is(testValue, matchResult) ], + this.willMatch + ) + } + + match(matchValue){ + return getMatchingKeyValuePair( + this.cases, matchValue, this.defaultValue + ) + } +} + +export function switcher(input){ + return new Switchem(input) +} +``` + +
+ +
+ +Tests + +```javascript +import { switcher } from './switcher.js' +import { tap } from './tap.js' + +test('with undefined', () => { + const result = switcher(undefined) + .is(x => x === 0, '0') + .is(x => x === undefined, 'UNDEFINED') + .default('3') + + expect(result).toBe('UNDEFINED') +}) + +test('happy', () => { + const a = true + const b = false + const result = switcher([ a, b ]) + .is([ false, false ], '0') + .is([ false, true ], '1') + .is([ true, true ], '2') + .default('3') + + expect(result).toBe('3') +}) + +test('can compare objects', () => { + const result = switcher({ a : 1 }) + .is({ a : 1 }, 'it is object') + .is('baz', 'it is baz') + .default('it is default') + + expect(result).toBe('it is object') +}) + +test('options are mixture of functions and values - input match function', () => { + const fn = switcher('foo').is('bar', 1) + .is('foo', x => x + 1) + .default(1000) + + expect(fn(2)).toBe(3) +}) + +test('options are mixture of functions and values - input match value', () => { + const result = switcher('bar').is('bar', 1) + .is('foo', x => x + 1) + .default(1000) + + expect(result).toBe(1) +}) + +test('return function if all options are functions', () => { + const fn = switcher('foo') + .is('bar', tap) + .is('foo', x => x + 1) + .default(9) + + expect(fn(2)).toBe(3) +}) + +const switchFn = input => + switcher(input) + .is(x => x.length && x.length === 7, 'has length of 7') + .is('baz', 'it is baz') + .default('it is default') + +test('works with function as condition', () => { + expect(switchFn([ 0, 1, 2, 3, 4, 5, 6 ])).toBe('has length of 7') +}) + +test('works with string as condition', () => { + expect(switchFn('baz')).toBe('it is baz') +}) + +test('fallback to default input when no matches', () => { + expect(switchFn(1)).toBe('it is default') +}) +``` + +
+ +
+ +TypeScript test + +```typescript +import { switcher } from 'rambda' + +describe('R.switcher', () => { + it('no transformation', () => { + const list = [1, 2, 3] + + const result = switcher(list.length) + .is(x => x < 2, 4) + .is(x => x < 4, 6) + .default(7) + + result // $ExpectType number + }) + it('with transformation', () => { + const list = [1, 2, 3] + type Stage = 'firstStage' | 'secondStage' | 'thirdStage' + + const result = switcher(list.length) + .is(x => x < 2, 'firstStage') + .is(x => x < 4, 'secondStage') + .default('thirdStage') + + result // $ExpectType Stage + }) +}) +``` + +
+ +[![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#switcher) + ### symmetricDifference ```typescript @@ -11703,7 +12506,9 @@ describe('R.symmetricDifference', () => { ```typescript -tail(input: T): T extends [any, ...infer U] ? U : [...T] +tail(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [any, ...infer U] ? U : T : T extends string ? string : never ``` It returns all but the first element of `input`. @@ -11723,8 +12528,9 @@ const result = [ All TypeScript definitions ```typescript -tail(input: T): T extends [any, ...infer U] ? U : [...T]; -tail(input: string): string; +tail(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [any, ...infer U] ? U : T : T extends string ? string : never; ```
@@ -11770,7 +12576,7 @@ test('tail', () => { TypeScript test ```typescript -import { tail } from 'rambda' +import { map, pipe, tail } from 'rambda' describe('R.tail', () => { it('with string', () => { @@ -11778,10 +12584,29 @@ describe('R.tail', () => { result // $ExpectType string }) - it('with list - one type', () => { - const result = tail([1, 2, 3]) - - result // $ExpectType number[] + it('with list - using const on short array', () => { + const result = pipe( + [1] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [number, number] }) it('with list - mixed types', () => { const result = tail(['foo', 'bar', 1, 2, 3]) @@ -13973,6 +14798,20 @@ describe('R.zipWith', () => { ## ❯ CHANGELOG +11.1.0 + +- Add `R.filterMap` - similar to Ruby `filter_map` + +- Add `R.mapChain` - when in `R.pipe` there are several `R.map` one after the other, then `R.mapChain` can be used instead. + +- Add `R.middle` - equal to `R.init` + `R.tail` + +- Add `R.random`, `R.shuffle`, `R.switcher`, `R.sum`, `R.delay` - imported from `Rambda` + +- Add index to `R.filter`/`R.reject` predicate signiture + +- Improve typing of `R.init`, `R.tail` + 11.0.1 - Add missing JS change for `R.includes` and `R.excludes` methods in `11.0.0` release. diff --git a/files/index.d.ts b/files/index.d.ts index 26ac4d31b..c672f0bb0 100644 --- a/files/index.d.ts +++ b/files/index.d.ts @@ -123,14 +123,25 @@ export type FlattenObject = object extends T } extends Record void> ? O : never; + +type isfn = (fn: (x: T) => boolean, y: T) => U; +type isfn2 = (fn: (x: T) => boolean, y: V) => U; + +interface Switchem { + is: isfn>; + default: (x: T) => T; +} +interface Switchem2 { + is: isfn2>; + default: (x: U) => U; +} + // API_MARKER /* Method: modifyItemAtIndex -Explanation: - -It replaces `index` in array `list` with the result of `replaceFn(list[i])`. +Explanation: It replaces `index` in array `list` with the result of `replaceFn(list[i])`. Example: @@ -486,7 +497,7 @@ export function filter( predicate: BooleanConstructor, ): (list: T[]) => ExcludeFalsy[]; export function filter( - predicate: (value: T) => boolean, + predicate: (value: T, index: number) => boolean, ): (list: T[]) => T[]; /* @@ -519,7 +530,7 @@ export function reject( predicate: BooleanConstructor, ): (list: T[]) => (null | undefined)[]; export function reject( - predicate: (value: T) => boolean, + predicate: (value: T, index: number) => boolean, ): (list: T[]) => T[]; /* @@ -862,8 +873,9 @@ Notes: */ // @SINGLE_MARKER -export function init(input: T): T extends readonly [...infer U, any] ? U : [...T]; -export function init(input: string): string; +export function init(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [...infer U, any] ? U : T : T extends string ? string : never; /* Method: intersection @@ -995,12 +1007,10 @@ Explanation: It returns the result of looping through `iterable` with `fn`. Example: ``` -const fn = x => x * 2 - -const iterable = [1, 2] -const obj = {a: 1, b: 2} - -const result = R.map(fn)(iterable), +const result = R.pipe( + [1, 2], + R.map(x => x * 2) +) // => [2, 4] ``` @@ -1017,6 +1027,85 @@ export function map( fn: (value: T[number]) => U, ): (data: T) => Mapped; +/* +Method: mapChain + +Explanation: Chained 2 or 3 `R.map` transformations as one. + +Example: + +``` +const result = R.pipe( + [1, 2], + R.mapChain( + x => x * 2, + x => [x, x > 3], + ) +) +// => [[2, false], [4, true]] +``` + +Categories: List + +Notes: + +*/ +// @SINGLE_MARKER +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, + fn3: (value: V, index: number) => Y, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, + fn3: (value: V) => Y, +): (data: T) => Mapped; + +/* +Method: filterMap + +Explanation: Same as `R.map` but it filters out `null/undefined` if returned from functor functions. + +Example: + +``` +const result = R.pipe( + [1, 2, 3], + R.filterMap(x => x > 1 ? x : null) +) +// => [2, 3] +``` + +Categories: List + +Notes: This function doesn't work with objects (use R.mapObject instead) + +*/ +// @SINGLE_MARKER +export function filterMap( + fn: (value: T[number], index: number) => U, +): (data: T) => Mapped>; +export function filterMap( + fn: (value: T[number]) => U, +): (data: T) => Mapped>; + /* Method: mapObject @@ -3072,6 +3161,31 @@ Notes: // @SINGLE_MARKER export function symmetricDifference(x: T[]): (y: T[]) => T[]; +/* +Method: middle + +Explanation: It returns all but the first and last element of `input`. + +Example: + +``` +const result = [ + R.middle([1, 2, 3, 4]), + R.middle('bar') +] +// => [[2, 3], 'a'] +``` + +Categories: List, String + +Notes: + +*/ +// @SINGLE_MARKER +export function middle(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : + T extends [any, ...infer U, any] ? U : T : T extends string ? string : never; + /* Method: tail @@ -3093,8 +3207,9 @@ Notes: */ // @SINGLE_MARKER -export function tail(input: T): T extends [any, ...infer U] ? U : [...T]; -export function tail(input: string): string; +export function tail(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [any, ...infer U] ? U : T : T extends string ? string : never; /* Method: take @@ -4881,5 +4996,107 @@ export function indexBy( property: K ): (list: T[]) => Record; +/* +Method: sum + +Explanation: + +Example: + +``` +const result = R.sum( + [1,2,3] +) +// => 6 +``` + +Categories: List + +Notes: + +*/ +// @SINGLE_MARKER +export function sum(list: number[]): number; + +/* +Method: delay + +Explanation: `setTimeout` as a promise that resolves to `RAMBDA_DELAY` string after `ms` milliseconds. + +Example: + +``` +``` + +Categories: + +Notes: + +*/ +// @SINGLE_MARKER +export function delay(ms: number): Promise<'RAMBDA_DELAY'>; + +/* +Method: shuffle + +Explanation: It returns a randomized copy of array. + +Example: + +``` +``` + +Categories: List + +Notes: + +*/ +// @SINGLE_MARKER +export function shuffle(list: T[]): T[]; + +/* +Method: random + +Explanation: It returns a random number between `min` inclusive and `max` inclusive. + +Example: + +``` +``` + +Categories: List + +Notes: + +*/ +// @SINGLE_MARKER +export function random(minInclusive: number, maxInclusive: number): number; + +/* +Method: switcher + +Explanation: + +Example: + +``` +const list = [1, 2, 3] + +const result = switcher(list.length) + .is(x => x < 2, 4) + .is(x => x < 4, 6) + .default(7) +// => 6 +``` + +Categories: Logic + +Notes: + +*/ +// @SINGLE_MARKER +export function switcher(valueToMatch: T): Switchem; +export function switcher(valueToMatch: T): Switchem2; + // API_MARKER_END // ============================================ diff --git a/index.d.cts b/index.d.cts index 15ca919e3..f346e9437 100644 --- a/index.d.cts +++ b/index.d.cts @@ -124,6 +124,19 @@ export type FlattenObject = object extends T ? O : never; +type isfn = (fn: (x: T) => boolean, y: T) => U; +type isfn2 = (fn: (x: T) => boolean, y: V) => U; + +interface Switchem { + is: isfn>; + default: (x: T) => T; +} +interface Switchem2 { + is: isfn2>; + default: (x: U) => U; +} + + /** * It adds new key-value pair to the object. */ @@ -272,6 +285,11 @@ export function createObjectFromKeys( */ export function defaultTo(defaultValue: T): (input: unknown) => T; +/** + * `setTimeout` as a promise that resolves to `RAMBDA_DELAY` string after `ms` milliseconds. + */ +export function delay(ms: number): Promise<'RAMBDA_DELAY'>; + /** * Helper function to be used with `R.sort` to sort list in descending order. */ @@ -353,13 +371,23 @@ export function filter( predicate: BooleanConstructor, ): (list: T[]) => ExcludeFalsy[]; export function filter( - predicate: (value: T) => boolean, + predicate: (value: T, index: number) => boolean, ): (list: T[]) => T[]; export function filterAsync( predicate: (value: T) => Promise, ): (list: T[]) => Promise; +/** + * Same as `R.map` but it filters out `null/undefined` if returned from functor functions. + */ +export function filterMap( + fn: (value: T[number], index: number) => U, +): (data: T) => Mapped>; +export function filterMap( + fn: (value: T[number]) => U, +): (data: T) => Mapped>; + /** * It loops over each property of `obj` and returns a new object with only those properties that satisfy the `predicate`. */ @@ -455,9 +483,6 @@ export function indexBy( property: K ): (list: T[]) => Record; -// API_MARKER_END -// ============================================ - /** * It uses `R.equals` for list of objects/arrays or native `indexOf` for any other case. */ @@ -466,8 +491,9 @@ export function indexOf(valueToFind: T): (list: T[]) => number; /** * It returns all but the last element of list or string `input`. */ -export function init(input: T): T extends readonly [...infer U, any] ? U : [...T]; -export function init(input: string): string; +export function init(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [...infer U, any] ? U : T : T extends string ? string : never; /** * It generates a new string from `inputWithTags` by replacing all `{{x}}` occurrences with values provided by `templateArguments`. @@ -538,6 +564,36 @@ export function mapAsync( fn: (value: T[number]) => Promise, ): (data: T) => Promise>; +/** + * Chained 2 or 3 `R.map` transformations as one. + */ +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, + fn3: (value: V, index: number) => Y, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, + fn3: (value: V) => Y, +): (data: T) => Mapped; + /** * It returns a copy of `obj` with keys transformed by `fn`. */ @@ -610,6 +666,13 @@ export function merge(source: Source): (data: T) => Merge; */ export function mergeTypes(x: T): MergeTypes; +/** + * It returns all but the first and last element of `input`. + */ +export function middle(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : + T extends [any, ...infer U, any] ? U : T : T extends string ? string : never; + /** * It returns the lesser value between `x` and `y` according to `compareFn` function. */ @@ -1791,6 +1854,11 @@ export function propOr(property: P, defaultValue: T): (obj: */ export function propSatisfies(predicate: (x: T) => boolean, property: string): (obj: Record) => boolean; +/** + * It returns a random number between `min` inclusive and `max` inclusive. + */ +export function random(minInclusive: number, maxInclusive: number): number; + /** * It returns list of numbers between `startInclusive` to `endInclusive` markers. */ @@ -2208,6 +2276,14 @@ export function split(separator: string | RegExp): (str: string) => string[]; */ export function splitEvery(sliceLength: number): (input: T[]) => (T[])[]; +export function sum(list: number[]): number; + +export function switcher(valueToMatch: T): Switchem; +export function switcher(valueToMatch: T): Switchem2; + +// API_MARKER_END +// ============================================ + /** * It returns all items that are in either of the lists, but not in both. * @@ -2218,8 +2294,9 @@ export function symmetricDifference(x: T[]): (y: T[]) => T[]; /** * It returns all but the first element of `input`. */ -export function tail(input: T): T extends [any, ...infer U] ? U : [...T]; -export function tail(input: string): string; +export function tail(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [any, ...infer U] ? U : T : T extends string ? string : never; /** * It returns the first `howMany` elements of `input`. diff --git a/index.d.ts b/index.d.ts index 15ca919e3..f346e9437 100644 --- a/index.d.ts +++ b/index.d.ts @@ -124,6 +124,19 @@ export type FlattenObject = object extends T ? O : never; +type isfn = (fn: (x: T) => boolean, y: T) => U; +type isfn2 = (fn: (x: T) => boolean, y: V) => U; + +interface Switchem { + is: isfn>; + default: (x: T) => T; +} +interface Switchem2 { + is: isfn2>; + default: (x: U) => U; +} + + /** * It adds new key-value pair to the object. */ @@ -272,6 +285,11 @@ export function createObjectFromKeys( */ export function defaultTo(defaultValue: T): (input: unknown) => T; +/** + * `setTimeout` as a promise that resolves to `RAMBDA_DELAY` string after `ms` milliseconds. + */ +export function delay(ms: number): Promise<'RAMBDA_DELAY'>; + /** * Helper function to be used with `R.sort` to sort list in descending order. */ @@ -353,13 +371,23 @@ export function filter( predicate: BooleanConstructor, ): (list: T[]) => ExcludeFalsy[]; export function filter( - predicate: (value: T) => boolean, + predicate: (value: T, index: number) => boolean, ): (list: T[]) => T[]; export function filterAsync( predicate: (value: T) => Promise, ): (list: T[]) => Promise; +/** + * Same as `R.map` but it filters out `null/undefined` if returned from functor functions. + */ +export function filterMap( + fn: (value: T[number], index: number) => U, +): (data: T) => Mapped>; +export function filterMap( + fn: (value: T[number]) => U, +): (data: T) => Mapped>; + /** * It loops over each property of `obj` and returns a new object with only those properties that satisfy the `predicate`. */ @@ -455,9 +483,6 @@ export function indexBy( property: K ): (list: T[]) => Record; -// API_MARKER_END -// ============================================ - /** * It uses `R.equals` for list of objects/arrays or native `indexOf` for any other case. */ @@ -466,8 +491,9 @@ export function indexOf(valueToFind: T): (list: T[]) => number; /** * It returns all but the last element of list or string `input`. */ -export function init(input: T): T extends readonly [...infer U, any] ? U : [...T]; -export function init(input: string): string; +export function init(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [...infer U, any] ? U : T : T extends string ? string : never; /** * It generates a new string from `inputWithTags` by replacing all `{{x}}` occurrences with values provided by `templateArguments`. @@ -538,6 +564,36 @@ export function mapAsync( fn: (value: T[number]) => Promise, ): (data: T) => Promise>; +/** + * Chained 2 or 3 `R.map` transformations as one. + */ +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U, index: number) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number], index: number) => U, + fn2: (value: U, index: number) => V, + fn3: (value: V, index: number) => Y, +): (data: T) => Mapped; +export function mapChain( + fn1: (value: T[number]) => U, + fn2: (value: U) => V, + fn3: (value: V) => Y, +): (data: T) => Mapped; + /** * It returns a copy of `obj` with keys transformed by `fn`. */ @@ -610,6 +666,13 @@ export function merge(source: Source): (data: T) => Merge; */ export function mergeTypes(x: T): MergeTypes; +/** + * It returns all but the first and last element of `input`. + */ +export function middle(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : + T extends [any, ...infer U, any] ? U : T : T extends string ? string : never; + /** * It returns the lesser value between `x` and `y` according to `compareFn` function. */ @@ -1791,6 +1854,11 @@ export function propOr(property: P, defaultValue: T): (obj: */ export function propSatisfies(predicate: (x: T) => boolean, property: string): (obj: Record) => boolean; +/** + * It returns a random number between `min` inclusive and `max` inclusive. + */ +export function random(minInclusive: number, maxInclusive: number): number; + /** * It returns list of numbers between `startInclusive` to `endInclusive` markers. */ @@ -2208,6 +2276,14 @@ export function split(separator: string | RegExp): (str: string) => string[]; */ export function splitEvery(sliceLength: number): (input: T[]) => (T[])[]; +export function sum(list: number[]): number; + +export function switcher(valueToMatch: T): Switchem; +export function switcher(valueToMatch: T): Switchem2; + +// API_MARKER_END +// ============================================ + /** * It returns all items that are in either of the lists, but not in both. * @@ -2218,8 +2294,9 @@ export function symmetricDifference(x: T[]): (y: T[]) => T[]; /** * It returns all but the first element of `input`. */ -export function tail(input: T): T extends [any, ...infer U] ? U : [...T]; -export function tail(input: string): string; +export function tail(input: T): T extends unknown[] ? + T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : + T extends [any, ...infer U] ? U : T : T extends string ? string : never; /** * It returns the first `howMany` elements of `input`. diff --git a/rambda.js b/rambda.js index 51adfb450..3ad5d98df 100644 --- a/rambda.js +++ b/rambda.js @@ -17,6 +17,7 @@ export * from './src/count.js' export * from './src/countBy.js' export * from './src/createObjectFromKeys.js' export * from './src/defaultTo.js' +export * from './src/delay.js' export * from './src/descend.js' export * from './src/difference.js' export * from './src/drop.js' @@ -32,6 +33,7 @@ export * from './src/excludes.js' export * from './src/exists.js' export * from './src/filter.js' export * from './src/filterAsync.js' +export * from './src/filterMap.js' export * from './src/filterObject.js' export * from './src/find.js' export * from './src/findIndex.js' @@ -56,6 +58,7 @@ export * from './src/last.js' export * from './src/lastIndexOf.js' export * from './src/map.js' export * from './src/mapAsync.js' +export * from './src/mapChain.js' export * from './src/mapKeys.js' export * from './src/mapObject.js' export * from './src/mapObjectAsync.js' @@ -65,6 +68,7 @@ export * from './src/match.js' export * from './src/maxBy.js' export * from './src/merge.js' export * from './src/mergeTypes.js' +export * from './src/middle.js' export * from './src/minBy.js' export * from './src/modifyItemAtIndex.js' export * from './src/modifyPath.js' @@ -87,6 +91,7 @@ export * from './src/prop.js' export * from './src/propEq.js' export * from './src/propOr.js' export * from './src/propSatisfies.js' +export * from './src/random.js' export * from './src/range.js' export * from './src/rangeDescending.js' export * from './src/reduce.js' @@ -104,6 +109,8 @@ export * from './src/sortObject.js' export * from './src/sortWith.js' export * from './src/split.js' export * from './src/splitEvery.js' +export * from './src/sum.js' +export * from './src/switcher.js' export * from './src/symmetricDifference.js' export * from './src/tail.js' export * from './src/take.js' diff --git a/source/addProp.spec.js b/source/addProp.spec.js index d1dd641f4..5ef8179bb 100644 --- a/source/addProp.spec.js +++ b/source/addProp.spec.js @@ -1,4 +1,4 @@ -import { addProp } from "./addProp.js" +import { addProp } from './addProp.js' test('happy', () => { const result = addProp('a', 1)({ b: 2 }) diff --git a/source/addPropToObjects.spec.js b/source/addPropToObjects.spec.js index 2a948808f..0a9893008 100644 --- a/source/addPropToObjects.spec.js +++ b/source/addPropToObjects.spec.js @@ -1,5 +1,5 @@ -import { pipe } from "./pipe.js" -import { addPropToObjects } from "./addPropToObjects.js" +import { pipe } from './pipe.js' +import { addPropToObjects } from './addPropToObjects.js' test('R.addPropToObjects', () => { let result = pipe( diff --git a/source/delay.js b/source/delay.js index f7ebcb436..6276977b0 100644 --- a/source/delay.js +++ b/source/delay.js @@ -1,9 +1,9 @@ -export const DELAY = 'RAMBDA_DELAY' +export const RAMBDA_DELAY = 'RAMBDA_DELAY' export function delay(ms) { return new Promise(resolve => { setTimeout(() => { - resolve(DELAY) + resolve(RAMBDA_DELAY) }, ms) }) } diff --git a/source/delay.spec.js b/source/delay.spec.js deleted file mode 100644 index 793513f9f..000000000 --- a/source/delay.spec.js +++ /dev/null @@ -1,5 +0,0 @@ -import { DELAY, delay } from './delay.js' - -test('usage with variables', async () => { - await expect(delay(500)).resolves.toBe(DELAY) -}) diff --git a/source/drop.js b/source/drop.js index 5e1ff66c7..658791a9d 100644 --- a/source/drop.js +++ b/source/drop.js @@ -1,3 +1,3 @@ -export function drop(howManyToDrop, ) { +export function drop(howManyToDrop) { return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) } diff --git a/source/filter-spec.ts b/source/filter-spec.ts index 4013d3d4d..9c9a5a2a8 100644 --- a/source/filter-spec.ts +++ b/source/filter-spec.ts @@ -4,14 +4,26 @@ const list = [1, 2, 3] describe('R.filter with array', () => { it('within pipe', () => { - const _result = pipe( + const result = pipe( list, filter(x => { x // $ExpectType number return x > 1 }), ) - _result // $ExpectType number[] + result // $ExpectType number[] + }) + + it('with index', () => { + const result = pipe( + list, + filter((x: number, i: number) => { + x // $ExpectType number + i // $ExpectType number + return x > 1 + }), + ) + result // $ExpectType number[] }) it('complex example', () => { @@ -50,8 +62,8 @@ describe('R.filter with array', () => { const filterBar = (x: T): x is Bar => { return typeof (x as Bar).b === 'string' } - const _result = pipe(testList, filter(filterBar)) - _result // $ExpectType Bar[] + const result = pipe(testList, filter(filterBar)) + result // $ExpectType Bar[] }) it('narrowing type - readonly', () => { @@ -66,14 +78,14 @@ describe('R.filter with array', () => { const filterBar = (x: T): x is Bar => { return typeof (x as Bar).b === 'string' } - const _result = pipe(testList, filter(filterBar)) - _result // $ExpectType Bar[] + const result = pipe(testList, filter(filterBar)) + result // $ExpectType Bar[] }) it('filtering NonNullable - list of objects', () => { const testList = [{ a: 1 }, { a: 2 }, false, { a: 3 }] - const _result = pipe(testList, filter(Boolean)) - _result // $ExpectType { a: number; }[] + const result = pipe(testList, filter(Boolean)) + result // $ExpectType { a: number; }[] }) it('filtering NonNullable - readonly', () => { diff --git a/source/filterMap-spec.ts b/source/filterMap-spec.ts new file mode 100644 index 000000000..548865071 --- /dev/null +++ b/source/filterMap-spec.ts @@ -0,0 +1,19 @@ +import { filterMap, pipe } from 'rambda' + +const list = [1, 2, 3] + +it('R.filterMap - within pipe', () => { + const result = pipe( + list, + x => x, + filterMap(x => { + x // $ExpectType number + return Math.random() > 0.5 ? String(x) : null + }), + filterMap(x => { + x // $ExpectType string + return Math.random() > 0.5 ? Number(x) : '' + }), + ) + result // $ExpectType number[] +}) diff --git a/source/filterMap.js b/source/filterMap.js new file mode 100644 index 000000000..5e09e2346 --- /dev/null +++ b/source/filterMap.js @@ -0,0 +1,5 @@ +import {mapFn} from './map.js' + +export function filterMap(fn) { + return list => mapFn(fn, list).filter(Boolean) +} diff --git a/source/filterMap.spec.js b/source/filterMap.spec.js new file mode 100644 index 000000000..d87a70643 --- /dev/null +++ b/source/filterMap.spec.js @@ -0,0 +1,7 @@ +import { filterMap } from './filterMap.js' + +const double = x => x > 1 ? x * 2 : null + +it('happy', () => { + expect(filterMap(double)([1, 2, 3])).toEqual([4, 6]) +}) diff --git a/source/init-spec.ts b/source/init-spec.ts index 61f0bf8c0..b0346cdb5 100644 --- a/source/init-spec.ts +++ b/source/init-spec.ts @@ -1,4 +1,4 @@ -import { init } from 'rambda' +import { map, pipe, init } from 'rambda' describe('R.init', () => { it('with string', () => { @@ -6,13 +6,32 @@ describe('R.init', () => { result // $ExpectType string }) - it('with list - one type', () => { - const result = init([1, 2, 3]) - - result // $ExpectType number[] + it('with list - using const on short array', () => { + const result = pipe( + [1] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3] as const, + map(x => x * 2), + init, + ) + result // $ExpectType [number, number] }) it('with list - mixed types', () => { - const result = init([1, 2, 3, 'foo', 'bar']) + const result = init(['foo', 'bar', 1, 2, 3]) result // $ExpectType (string | number)[] }) diff --git a/source/map-spec.ts b/source/map-spec.ts index 4152beab3..b52bab589 100644 --- a/source/map-spec.ts +++ b/source/map-spec.ts @@ -2,7 +2,7 @@ import { map, pipe } from 'rambda' const list = [1, 2, 3] -it('R.map - within pipe', () => { +it('R.map', () => { const result = pipe( list, x => x, @@ -14,6 +14,19 @@ it('R.map - within pipe', () => { result // $ExpectType string[] }) +it('R.map - index in functor', () => { + const result = pipe( + list, + x => x, + map((x, i) => { + x // $ExpectType number + i // $ExpectType number + return String(x) + }), + ) + result // $ExpectType string[] +}) + it('R.map - without pipe', () => { map(x => { x // $ExpectType unknown diff --git a/source/mapChain-spec.ts b/source/mapChain-spec.ts new file mode 100644 index 000000000..c2c2edcc2 --- /dev/null +++ b/source/mapChain-spec.ts @@ -0,0 +1,60 @@ +import { mapChain, pipe } from 'rambda' + +const list = [1, 2, 3] + +it('R.mapChain', () => { + const result = pipe( + list, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + x => { + x // $ExpectType string + return x !== 'foo' + }, + ), + ) + result // $ExpectType boolean[] +}) + +it('R.mapChain - with index', () => { + const result = pipe( + list, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + (x, i) => { + i // $ExpectType number + x // $ExpectType string + return x !== 'foo' + }, + ), + ) + result // $ExpectType boolean[] +}) + +it('R.mapChain - 3 functions', () => { + const result = pipe( + list, + x => x, + mapChain( + x => { + x // $ExpectType number + return String(x) + }, + x => { + x // $ExpectType string + return x !== 'foo' + }, + x => { + x // $ExpectType boolean + return x ? 'foo' : 'bar' + }, + ), + ) + result // $ExpectType ("foo" | "bar")[] +}) diff --git a/source/mapChain.js b/source/mapChain.js new file mode 100644 index 000000000..1543c9a22 --- /dev/null +++ b/source/mapChain.js @@ -0,0 +1,11 @@ +import { mapFn } from './map.js'; + +export function mapChain(...fns) { + return list => { + let result = list.slice() + fns.forEach((fn) => { + result = mapFn(fn, result) + }) + return result + } +} diff --git a/source/mapChain.spec.js b/source/mapChain.spec.js new file mode 100644 index 000000000..1058b6084 --- /dev/null +++ b/source/mapChain.spec.js @@ -0,0 +1,7 @@ +import { mapChain } from './mapChain.js' + +const double = x => x * 2 + +it('happy', () => { + expect(mapChain(double, double, double)([1, 2, 3])).toEqual([8, 16, 24]) +}) diff --git a/source/mapKeys.spec.js b/source/mapKeys.spec.js index 47f443e1b..6358439b9 100644 --- a/source/mapKeys.spec.js +++ b/source/mapKeys.spec.js @@ -1,4 +1,4 @@ -import { mapKeys } from "./mapKeys.js" +import { mapKeys } from './mapKeys.js' test('happy', () => { const result = mapKeys((prop, x) => `${ prop }-${x}`)({a:1, b: 2 }) diff --git a/source/middle-spec.ts b/source/middle-spec.ts new file mode 100644 index 000000000..529ae45ca --- /dev/null +++ b/source/middle-spec.ts @@ -0,0 +1,38 @@ +import { map, middle, pipe } from 'rambda' + +describe('R.middle', () => { + it('with string', () => { + const result = middle('foo') + + result // $ExpectType string + }) + it('with list - using const on short array', () => { + const result = pipe( + [1, 2] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3, 4] as const, + map(x => x * 2), + middle, + ) + result // $ExpectType [number, number] + }) + it('with list - mixed types', () => { + const result = middle(['foo', 'bar', 1, 2, 3]) + + result // $ExpectType (string | number)[] + }) +}) diff --git a/source/middle.js b/source/middle.js new file mode 100644 index 000000000..be537f1bb --- /dev/null +++ b/source/middle.js @@ -0,0 +1,6 @@ +import { init } from './init.js' +import { tail } from './tail.js' + +export function middle(listOrString) { + return tail(init(listOrString)) +} diff --git a/source/middle.spec.js b/source/middle.spec.js new file mode 100644 index 000000000..a390be344 --- /dev/null +++ b/source/middle.spec.js @@ -0,0 +1,13 @@ +import { middle } from './middle' + +test('middle', () => { + expect(middle([1, 2, 3])).toEqual([2]) + expect(middle([1, 2])).toEqual([]) + expect(middle([1])).toEqual([]) + expect(middle([])).toEqual([]) + + expect(middle('abc')).toBe('b') + expect(middle('ab')).toBe('') + expect(middle('a')).toBe('') + expect(middle('')).toBe('') +}) diff --git a/source/pluck-spec.ts b/source/pluck-spec.ts index 1d048373a..3c15710ec 100644 --- a/source/pluck-spec.ts +++ b/source/pluck-spec.ts @@ -1,4 +1,4 @@ -import { pipe, pluck } from "rambda"; +import { pipe, pluck } from 'rambda'; it("R.pluck", () => { const input = [ diff --git a/source/random.js b/source/random.js new file mode 100644 index 000000000..ebc45fbb2 --- /dev/null +++ b/source/random.js @@ -0,0 +1,3 @@ +export function random(min, max){ + return Math.floor(Math.random() * (max - min + 1)) + min +} diff --git a/source/random.spec.js b/source/random.spec.js new file mode 100644 index 000000000..f44184ff8 --- /dev/null +++ b/source/random.spec.js @@ -0,0 +1,9 @@ +import { random } from './random.js' +import { range } from './range.js' +import { uniq } from './uniq.js' + +test('happy', () => { + const result = uniq(range(100).map(() => random(0, 3))).sort() + expect(result).toEqual([0,1,2,3]) +}) + diff --git a/source/reject-spec.ts b/source/reject-spec.ts index f43e9e49d..6fa72992c 100644 --- a/source/reject-spec.ts +++ b/source/reject-spec.ts @@ -12,6 +12,17 @@ describe('R.reject with array', () => { }), ) result // $ExpectType number[] + }) + it('with index', () => { + const result = pipe( + list, + reject((x: number, i: number) => { + x // $ExpectType number + i // $ExpectType number + return x > 1 + }), + ) + result // $ExpectType number[] }) it('narrowing type', () => { interface Foo { diff --git a/source/sortByDescending.js b/source/sortByDescending.js index 86d702d81..46522d4af 100644 --- a/source/sortByDescending.js +++ b/source/sortByDescending.js @@ -1,4 +1,4 @@ -import { sortByFn } from "./sortBy.js"; +import { sortByFn } from './sortBy.js'; export function sortByDescending(sortFn) { return list => sortByFn(sortFn, list, true) diff --git a/source/sum.js b/source/sum.js new file mode 100644 index 000000000..fc898956a --- /dev/null +++ b/source/sum.js @@ -0,0 +1,3 @@ +export function sum(list){ + return list.reduce((acc, cur) => acc + cur, 0) +} diff --git a/source/sum.spec.js b/source/sum.spec.js new file mode 100644 index 000000000..43d85963c --- /dev/null +++ b/source/sum.spec.js @@ -0,0 +1,5 @@ +import { sum } from './sum.js' + +test('happy', () => { + expect(sum([1,2,3])).toEqual(6) +}) diff --git a/source/switcher-spec.ts b/source/switcher-spec.ts new file mode 100644 index 000000000..73b3fc80b --- /dev/null +++ b/source/switcher-spec.ts @@ -0,0 +1,25 @@ +import { switcher } from 'rambda' + +describe('R.switcher', () => { + it('no transformation', () => { + const list = [1, 2, 3] + + const result = switcher(list.length) + .is(x => x < 2, 4) + .is(x => x < 4, 6) + .default(7) + + result // $ExpectType number + }) + it('with transformation', () => { + const list = [1, 2, 3] + type Stage = 'firstStage' | 'secondStage' | 'thirdStage' + + const result = switcher(list.length) + .is(x => x < 2, 'firstStage') + .is(x => x < 4, 'secondStage') + .default('thirdStage') + + result // $ExpectType Stage + }) +}) diff --git a/source/switcher.js b/source/switcher.js new file mode 100644 index 000000000..a4aa23b8e --- /dev/null +++ b/source/switcher.js @@ -0,0 +1,78 @@ +import { equals } from './equals.js' + +const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined + +const getMatchingKeyValuePair = ( + cases, testValue, defaultValue +) => { + let iterationValue + + for (let index = 0; index < cases.length; index++){ + iterationValue = cases[ index ].test(testValue) + + if (iterationValue !== NO_MATCH_FOUND){ + return iterationValue + } + } + + return defaultValue +} + +const isEqual = (testValue, matchValue) => { + const willReturn = + typeof testValue === 'function' ? + testValue(matchValue) : + equals(testValue)(matchValue) + + return willReturn +} + +const is = (testValue, matchResult = true) => ({ + key : testValue, + test : matchValue => + isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND, +}) + +class Switchem{ + constructor( + defaultValue, cases, willMatch + ){ + if (cases === undefined && willMatch === undefined){ + this.cases = [] + this.defaultValue = undefined + this.willMatch = defaultValue + } else { + this.cases = cases + this.defaultValue = defaultValue + this.willMatch = willMatch + } + + return this + } + + default(defaultValue){ + const holder = new Switchem( + defaultValue, this.cases, this.willMatch + ) + + return holder.match(this.willMatch) + } + + is(testValue, matchResult){ + return new Switchem( + this.defaultValue, + [ ...this.cases, is(testValue, matchResult) ], + this.willMatch + ) + } + + match(matchValue){ + return getMatchingKeyValuePair( + this.cases, matchValue, this.defaultValue + ) + } +} + +export function switcher(input){ + return new Switchem(input) +} diff --git a/source/switcher.spec.js b/source/switcher.spec.js new file mode 100644 index 000000000..ced4c07d8 --- /dev/null +++ b/source/switcher.spec.js @@ -0,0 +1,75 @@ +import { switcher } from './switcher.js' +import { tap } from './tap.js' + +test('with undefined', () => { + const result = switcher(undefined) + .is(x => x === 0, '0') + .is(x => x === undefined, 'UNDEFINED') + .default('3') + + expect(result).toBe('UNDEFINED') +}) + +test('happy', () => { + const a = true + const b = false + const result = switcher([ a, b ]) + .is([ false, false ], '0') + .is([ false, true ], '1') + .is([ true, true ], '2') + .default('3') + + expect(result).toBe('3') +}) + +test('can compare objects', () => { + const result = switcher({ a : 1 }) + .is({ a : 1 }, 'it is object') + .is('baz', 'it is baz') + .default('it is default') + + expect(result).toBe('it is object') +}) + +test('options are mixture of functions and values - input match function', () => { + const fn = switcher('foo').is('bar', 1) + .is('foo', x => x + 1) + .default(1000) + + expect(fn(2)).toBe(3) +}) + +test('options are mixture of functions and values - input match value', () => { + const result = switcher('bar').is('bar', 1) + .is('foo', x => x + 1) + .default(1000) + + expect(result).toBe(1) +}) + +test('return function if all options are functions', () => { + const fn = switcher('foo') + .is('bar', tap) + .is('foo', x => x + 1) + .default(9) + + expect(fn(2)).toBe(3) +}) + +const switchFn = input => + switcher(input) + .is(x => x.length && x.length === 7, 'has length of 7') + .is('baz', 'it is baz') + .default('it is default') + +test('works with function as condition', () => { + expect(switchFn([ 0, 1, 2, 3, 4, 5, 6 ])).toBe('has length of 7') +}) + +test('works with string as condition', () => { + expect(switchFn('baz')).toBe('it is baz') +}) + +test('fallback to default input when no matches', () => { + expect(switchFn(1)).toBe('it is default') +}) diff --git a/source/tail-spec.ts b/source/tail-spec.ts index 0e9e03397..902ed5189 100644 --- a/source/tail-spec.ts +++ b/source/tail-spec.ts @@ -1,4 +1,4 @@ -import { tail } from 'rambda' +import { map, pipe, tail } from 'rambda' describe('R.tail', () => { it('with string', () => { @@ -6,10 +6,29 @@ describe('R.tail', () => { result // $ExpectType string }) - it('with list - one type', () => { - const result = tail([1, 2, 3]) - - result // $ExpectType number[] + it('with list - using const on short array', () => { + const result = pipe( + [1] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [] + }) + it('with list - using const on empty array', () => { + const result = pipe( + [] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [] + }) + it('with list - using const', () => { + const result = pipe( + [1, 2, 3] as const, + map(x => x * 2), + tail, + ) + result // $ExpectType [number, number] }) it('with list - mixed types', () => { const result = tail(['foo', 'bar', 1, 2, 3]) diff --git a/src/delay.js b/src/delay.js new file mode 100644 index 000000000..6276977b0 --- /dev/null +++ b/src/delay.js @@ -0,0 +1,9 @@ +export const RAMBDA_DELAY = 'RAMBDA_DELAY' + +export function delay(ms) { + return new Promise(resolve => { + setTimeout(() => { + resolve(RAMBDA_DELAY) + }, ms) + }) +} diff --git a/src/drop.js b/src/drop.js index 5e1ff66c7..658791a9d 100644 --- a/src/drop.js +++ b/src/drop.js @@ -1,3 +1,3 @@ -export function drop(howManyToDrop, ) { +export function drop(howManyToDrop) { return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) } diff --git a/src/filterMap.js b/src/filterMap.js new file mode 100644 index 000000000..5e09e2346 --- /dev/null +++ b/src/filterMap.js @@ -0,0 +1,5 @@ +import {mapFn} from './map.js' + +export function filterMap(fn) { + return list => mapFn(fn, list).filter(Boolean) +} diff --git a/src/mapChain.js b/src/mapChain.js new file mode 100644 index 000000000..1543c9a22 --- /dev/null +++ b/src/mapChain.js @@ -0,0 +1,11 @@ +import { mapFn } from './map.js'; + +export function mapChain(...fns) { + return list => { + let result = list.slice() + fns.forEach((fn) => { + result = mapFn(fn, result) + }) + return result + } +} diff --git a/src/middle.js b/src/middle.js new file mode 100644 index 000000000..be537f1bb --- /dev/null +++ b/src/middle.js @@ -0,0 +1,6 @@ +import { init } from './init.js' +import { tail } from './tail.js' + +export function middle(listOrString) { + return tail(init(listOrString)) +} diff --git a/src/random.js b/src/random.js new file mode 100644 index 000000000..ebc45fbb2 --- /dev/null +++ b/src/random.js @@ -0,0 +1,3 @@ +export function random(min, max){ + return Math.floor(Math.random() * (max - min + 1)) + min +} diff --git a/src/sortByDescending.js b/src/sortByDescending.js index 86d702d81..46522d4af 100644 --- a/src/sortByDescending.js +++ b/src/sortByDescending.js @@ -1,4 +1,4 @@ -import { sortByFn } from "./sortBy.js"; +import { sortByFn } from './sortBy.js'; export function sortByDescending(sortFn) { return list => sortByFn(sortFn, list, true) diff --git a/src/sum.js b/src/sum.js new file mode 100644 index 000000000..fc898956a --- /dev/null +++ b/src/sum.js @@ -0,0 +1,3 @@ +export function sum(list){ + return list.reduce((acc, cur) => acc + cur, 0) +} diff --git a/src/switcher.js b/src/switcher.js new file mode 100644 index 000000000..a4aa23b8e --- /dev/null +++ b/src/switcher.js @@ -0,0 +1,78 @@ +import { equals } from './equals.js' + +const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined + +const getMatchingKeyValuePair = ( + cases, testValue, defaultValue +) => { + let iterationValue + + for (let index = 0; index < cases.length; index++){ + iterationValue = cases[ index ].test(testValue) + + if (iterationValue !== NO_MATCH_FOUND){ + return iterationValue + } + } + + return defaultValue +} + +const isEqual = (testValue, matchValue) => { + const willReturn = + typeof testValue === 'function' ? + testValue(matchValue) : + equals(testValue)(matchValue) + + return willReturn +} + +const is = (testValue, matchResult = true) => ({ + key : testValue, + test : matchValue => + isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND, +}) + +class Switchem{ + constructor( + defaultValue, cases, willMatch + ){ + if (cases === undefined && willMatch === undefined){ + this.cases = [] + this.defaultValue = undefined + this.willMatch = defaultValue + } else { + this.cases = cases + this.defaultValue = defaultValue + this.willMatch = willMatch + } + + return this + } + + default(defaultValue){ + const holder = new Switchem( + defaultValue, this.cases, this.willMatch + ) + + return holder.match(this.willMatch) + } + + is(testValue, matchResult){ + return new Switchem( + this.defaultValue, + [ ...this.cases, is(testValue, matchResult) ], + this.willMatch + ) + } + + match(matchValue){ + return getMatchingKeyValuePair( + this.cases, matchValue, this.defaultValue + ) + } +} + +export function switcher(input){ + return new Switchem(input) +}