From dd3251f6a78dc9080ab438aa330f363c41023b18 Mon Sep 17 00:00:00 2001 From: Shivam Singla Date: Thu, 4 Jun 2020 19:58:51 +0530 Subject: [PATCH 1/5] add functions head, groupWith --- groupWith.ts | 18 ++++++++++++++++++ head.ts | 13 +++++++++++++ mod.ts | 2 ++ nth.ts | 8 +++++++- specs/groupWith.spec.ts | 40 ++++++++++++++++++++++++++++++++++++++++ specs/head.spec.ts | 26 ++++++++++++++++++++++++++ utils/is.ts | 4 ++-- 7 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 groupWith.ts create mode 100644 head.ts create mode 100644 specs/groupWith.spec.ts create mode 100644 specs/head.spec.ts diff --git a/groupWith.ts b/groupWith.ts new file mode 100644 index 0000000..aded976 --- /dev/null +++ b/groupWith.ts @@ -0,0 +1,18 @@ +import { Predicate2, Curry2 } from "./utils/types.ts" +import curryN from "./utils/curry_n.ts" +import { slice } from "./slice.ts" + +function _groupWith(predicate: Predicate2, functor: T[] | string) { + const result: T[][] | string[] = [] + const len = functor.length + let i = 0 + while(i < len) { + let j = i + 1 + while(j < len && predicate(functor[j - 1], functor[j])) j++ + result.push(slice(i, j, functor) as (T[] & string)) + i = j + } + return result +} + +export const groupWith: Curry2 = curryN(2, _groupWith) diff --git a/head.ts b/head.ts new file mode 100644 index 0000000..570f08c --- /dev/null +++ b/head.ts @@ -0,0 +1,13 @@ +import { nth } from './nth.ts' + +/** + * Returns the first element of the given list or string. In some libraries + * this function is named `first`. + * + * Fae.head(['fi', 'fo', 'fum']); //=> 'fi' + * Fae.head([]); //=> undefined + * + * Fae.head('abc'); //=> 'a' + * Fae.head(''); //=> '' + */ +export const head = nth(0) diff --git a/mod.ts b/mod.ts index 467ebdb..4dc10cb 100644 --- a/mod.ts +++ b/mod.ts @@ -53,6 +53,8 @@ export { findLast } from './findLast.ts' export { findLastIndex } from './findLastIndex.ts' export { flip } from './flip.ts' export { Pair, fromPairs } from './fromPairs.ts' +export { groupWith } from './groupWith.ts' +export { head } from './head.ts' export { identity } from './identity.ts' export { inc } from './inc.ts' export { insert } from './insert.ts' diff --git a/nth.ts b/nth.ts index e9a29aa..e563197 100644 --- a/nth.ts +++ b/nth.ts @@ -13,7 +13,13 @@ function _nth(index: number, functor: FunctorWithArLk | string) { else throwFunctorError() index = index < 0 ? index + f.length : index - return f[index] + return( + f[index] + ? f[index] + : isString(functor) + ? '' + : f[index] + ) } /** diff --git a/specs/groupWith.spec.ts b/specs/groupWith.spec.ts new file mode 100644 index 0000000..474c5d1 --- /dev/null +++ b/specs/groupWith.spec.ts @@ -0,0 +1,40 @@ +import { describe, it } from "./_describe.ts" +import { groupWith, equals } from '../mod.ts' +import { eq } from "./utils/utils.ts" + +describe('groupWith', () => { + + it('should split the list into groups according to the grouping function', () => { + eq(groupWith(equals, [1, 2, 2, 3]), [[1], [2, 2], [3]]) + eq(groupWith(equals, [1, 1, 1, 1]), [[1, 1, 1, 1]]) + eq(groupWith(equals, [1, 2, 3, 4]), [[1], [2], [3], [4]]) + }) + + it('should split the list into "streaks" testing adjacent elements', () => { + // @ts-ignore + const isConsecutive = function(a, b) { return a + 1 === b; } + eq(groupWith(isConsecutive, []), []) + eq(groupWith(isConsecutive, [4, 3, 2, 1]), [[4], [3], [2], [1]]) + eq(groupWith(isConsecutive, [1, 2, 3, 4]), [[1, 2, 3, 4]]) + eq(groupWith(isConsecutive, [1, 2, 2, 3]), [[1, 2], [2, 3]]) + eq(groupWith(isConsecutive, [1, 2, 9, 3, 4]), [[1, 2], [9], [3, 4]]) + }) + + it('should return an empty array if given an empty array', () => { + eq(groupWith(equals, []), []) + }) + + // TODO: + // it('can be turned into the original list through concatenation', () => { + // var list = [1, 1, 2, 3, 4, 4, 5, 5] + // eq(R.unnest(groupWith(equals, list)), list) + // eq(R.unnest(groupWith(R.complement(equals), list)), list) + // eq(R.unnest(groupWith(R.T, list)), list) + // eq(R.unnest(groupWith(R.F, list)), list) + // }) + + it('should also work on strings', () => { + eq(groupWith(equals)('Mississippi'), ['M','i','ss','i','ss','i','pp','i']) + }) + +}) diff --git a/specs/head.spec.ts b/specs/head.spec.ts new file mode 100644 index 0000000..625cf6b --- /dev/null +++ b/specs/head.spec.ts @@ -0,0 +1,26 @@ +import { describe, it } from "./_describe.ts" +import { head } from '../mod.ts' +import { eq, thr } from "./utils/utils.ts" + +describe('head', () => { + + it('should return the first element of an ordered collection', () => { + eq(head([1, 2, 3]), 1) + eq(head([2, 3]), 2) + eq(head([3]), 3) + eq(head([]), undefined) + + eq(head('abc'), 'a') + eq(head('bc'), 'b') + eq(head('c'), 'c') + eq(head(''), '') + }) + + it('should throw if applied to null or undefined', () => { + // @ts-ignore + thr(() => head(null), 'The functor should be an array like or iterable/iterator') + // @ts-ignore + thr(() => head(undefined), 'The functor should be an array like or iterable/iterator') + }) + +}) diff --git a/utils/is.ts b/utils/is.ts index 3346436..11b5f46 100644 --- a/utils/is.ts +++ b/utils/is.ts @@ -40,11 +40,11 @@ export function isArrayLike(x: any): x is ArrayLike { } export function isIterable(x: any): x is Iterable { - return Symbol.iterator in Object(x) || isFunction(x.next) + return Symbol.iterator in Object(x) } export function isIterator(x: any): x is Iterator { - return isFunction(x.next) + return x && isFunction(x.next) } export function isTransformer(s: any): s is Transformer { From fc8243a36620367532d53d1f10aae1f02b588122 Mon Sep 17 00:00:00 2001 From: Shivam Singla Date: Sun, 7 Jun 2020 23:53:34 +0530 Subject: [PATCH 2/5] add indexOf --- indexOf.ts | 56 +++++++++++++++++++++ mod.ts | 1 + specs/indexOf.spec.ts | 112 ++++++++++++++++++++++++++++++++++++++++++ utils/is.ts | 2 +- 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 indexOf.ts create mode 100644 specs/indexOf.spec.ts diff --git a/indexOf.ts b/indexOf.ts new file mode 100644 index 0000000..81c4096 --- /dev/null +++ b/indexOf.ts @@ -0,0 +1,56 @@ +import { isNumber } from './utils/is.ts' +import curryN from "./utils/curry_n.ts" +import { Curry2 } from "./utils/types.ts" +import { equals } from './equals.ts' + +function _indexOf(value: T, list: T[]) { + switch(typeof value) { + case 'number': { + if(value === 0) { + // handles +0 and -0 + const inf = 1 / value + for (let i = 0; i < list.length; i++) { + const x: any = list[i] + if(x === 0 && 1 / x === inf) return i + } + return -1 + } + else if(value !== value) { + // handles NaN + for (let i = 0; i < list.length; i++) { + const x: any = list[i] + if(isNaN(x)) return i + } + return -1 + } + } + case 'string': + case 'boolean': + case 'function': + case 'undefined': + return list.indexOf(value) + + case 'object': + if (value === null) { + return list.indexOf(value) + } + } + + + let idx = -1 + list.forEach((a, i) => idx = equals(value, a) ? i : idx) + return idx +} + +/** + * Returns the position of the first occurrence of `value` in `list`, or -1 + * if the item is not included in the array. [`Fae.equals`](#equals) is used to + * determine equality. + * + * Fae.indexOf(3, [1,2,3,4]); //=> 2 + * Fae.indexOf(10, [1,2,3,4]); //=> -1 + * Fae.indexOf(0, [1, 2, 3, 0, -0, NaN]); //=> 3 + * Fae.indexOf(-0, [1, 2, 3, 0, -0, NaN]); //=> 4 + * Fae.indexOf(NaN, [1, 2, 3, 0, -0, NaN]); //=> 5 + */ +export const indexOf: Curry2 = curryN(2, _indexOf) diff --git a/mod.ts b/mod.ts index 5236be8..adbd1cc 100644 --- a/mod.ts +++ b/mod.ts @@ -59,6 +59,7 @@ export { groupWith } from './groupWith.ts' export { head } from './head.ts' export { identity } from './identity.ts' export { inc } from './inc.ts' +export { indexOf } from './indexOf.ts' export { insert } from './insert.ts' export { join } from './join.ts' export { diff --git a/specs/indexOf.spec.ts b/specs/indexOf.spec.ts new file mode 100644 index 0000000..6d051e0 --- /dev/null +++ b/specs/indexOf.spec.ts @@ -0,0 +1,112 @@ +import { describe, it, expect } from "./_describe.ts" +import { indexOf, equals } from '../mod.ts' +import { eq } from "./utils/utils.ts" + + +describe('indexOf', () => { + it("should return a number indicating an object's position in a list", () => { + const list = [0, 10, 20, 30] + eq(indexOf(30, list), 3) + }) + + it('should return -1 if the object is not in the list', () => { + const list = [0, 10, 20, 30] + eq(indexOf(40, list), -1) + }) + + const input = [1, 2, 3, 4, 5] + it('should return the index of the first item', () => { + eq(indexOf(1, input), 0) + }) + + it('should return the index of the last item', () => { + eq(indexOf(5, input), 4) + }) + + const list = [1, 2, 3] + list[-2] = 4 // Throw a wrench in the gears by assigning a non-valid array index as object property. + + it('should find 1', () => { + eq(indexOf(1, list), 0) + }) + + it('should find 1 and is result strictly it', () => { + eq(indexOf(1, list), 0) + }) + + it('should not find 4', () => { + eq(indexOf(4, list), -1) + }) + + it('should not consider "1" equal to 1', () => { + eq(indexOf('1', list), -1) + }) + + it('should return -1 for an empty array', () => { + eq(indexOf('x', []), -1) + }) + + it('should have equals semantics', () => { + class Just { + private value: any + constructor(x: any) { this.value = x } + equals(x: any) { + return x instanceof Just && equals(x.value, this.value) + } + } + + eq(indexOf(0, [-0]), -1) + eq(indexOf(-0, [0]), -1) + eq(indexOf(NaN, [NaN]), 0) + eq(indexOf(new Just([42]), [new Just([42])]), 0) + }) + + // it('dispatches to `indexOf` method', () => { + // function Empty() {} + // Empty.prototype.indexOf = R.always(-1) + + // function List(head, tail) { + // this.head = head + // this.tail = tail + // } + // List.prototype.indexOf = function(x) { + // const idx = this.tail.indexOf(x) + // return this.head === x ? 0 : idx >= 0 ? 1 + idx : -1 + // } + + // const list = new List('b', + // new List('a', + // new List('n', + // new List('a', + // new List('n', + // new List('a', + // new Empty() + // ) + // ) + // ) + // ) + // ) + // ) + + // eq(indexOf('a', 'banana'), 1) + // eq(indexOf('x', 'banana'), -1) + // eq(indexOf('a', list), 1) + // eq(indexOf('x', list), -1) + // }) + + it('should find function, compared by identity', () => { + const f = () => {} + const g = () => {} + const list = [g, f, g, f] + eq(indexOf(f, list), 1) + }) + + it('should not find function, compared by identity', () => { + const f = () => {} + const g = () => {} + const h = () => {} + const list = [g, f] + eq(indexOf(h, list), -1) + }) + +}) diff --git a/utils/is.ts b/utils/is.ts index 11b5f46..e5616d4 100644 --- a/utils/is.ts +++ b/utils/is.ts @@ -5,7 +5,7 @@ export function is(x: any, type: string) { return Object.prototype.toString.call(x) === `[object ${type}]` } -export function isNumber(x: any): x is Number { +export function isNumber(x: any): x is Number | number { return is(x, 'Number') } From f7d91da5d7ccbe6c0a79cff8014d5847cecf09bf Mon Sep 17 00:00:00 2001 From: ch-shubham Date: Fri, 12 Jun 2020 21:54:57 +0530 Subject: [PATCH 3/5] https://github.com/Jozty/Fae/issues/26 --- andThen.ts | 11 +++++++++++ mod.ts | 1 + specs/andThen.spec.ts | 28 ++++++++++++++++++++++++++++ utils/assertPromise.ts | 7 +++++++ 4 files changed, 47 insertions(+) create mode 100644 andThen.ts create mode 100644 specs/andThen.spec.ts create mode 100644 utils/assertPromise.ts diff --git a/andThen.ts b/andThen.ts new file mode 100644 index 0000000..0400fbb --- /dev/null +++ b/andThen.ts @@ -0,0 +1,11 @@ +import curryN from "./utils/curry_n.ts" +import { Func, Curry2 } from "./utils/types.ts" +import _assertPromise from "./utils/assertPromise.ts" + +function _andThen(f: Func, p: any) { + _assertPromise('andThen', p); + + return p.then(f); +} + +export const andThen: Curry2> = curryN(2, _andThen) \ No newline at end of file diff --git a/mod.ts b/mod.ts index 62844e4..458f981 100644 --- a/mod.ts +++ b/mod.ts @@ -21,6 +21,7 @@ export { all } from './all.ts' export { allPass } from './allPass.ts' export { always } from './always.ts' export { and } from './and.ts' +export { andThen } from './andThen.ts' export { any } from './any.ts' export { anyPass } from './anyPass.ts' export { ap } from './ap.ts' diff --git a/specs/andThen.spec.ts b/specs/andThen.spec.ts new file mode 100644 index 0000000..687eca2 --- /dev/null +++ b/specs/andThen.spec.ts @@ -0,0 +1,28 @@ +import { describe, it } from "./_describe.ts" +import { andThen } from '../mod.ts' +import { eq } from "./utils/utils.ts" + +describe('andThen', () => { + it('invokes then on the promise with the function passed to it', function() { + andThen( + function(n) { + eq(n, 1) + }, + Promise.resolve(1) + ) + }) + + it('is not dependent on a particular promise implementation', function() { + let thennable = { + then: function(f: any) { + return f(42) + } + } + + let f = function(n: any) { + eq(n, 42) + } + + andThen(f, thennable) + }) +}) \ No newline at end of file diff --git a/utils/assertPromise.ts b/utils/assertPromise.ts new file mode 100644 index 0000000..abdcf90 --- /dev/null +++ b/utils/assertPromise.ts @@ -0,0 +1,7 @@ +import { isFunction } from './is.ts' + +export default function _assertPromise(name: any, p: any) { + if (p == null || !isFunction(p.then)) { + throw new TypeError('`' + name + '` expected a Promise, received ' + p.toString()) + } +} \ No newline at end of file From f7470f04384ca08b4aaa34f9cce8853c3f246b90 Mon Sep 17 00:00:00 2001 From: Shivam Singla Date: Fri, 12 Jun 2020 23:33:08 +0530 Subject: [PATCH 4/5] add support for async tests --- specs/_async.ts | 16 ++++++++++++++++ specs/_run.ts | 6 ++++++ 2 files changed, 22 insertions(+) create mode 100644 specs/_async.ts diff --git a/specs/_async.ts b/specs/_async.ts new file mode 100644 index 0000000..d690806 --- /dev/null +++ b/specs/_async.ts @@ -0,0 +1,16 @@ + +export function it(fun: Function) { + return async function() { + let done: Function = () => void 0 + const p = new Promise(resolve => { + let d = () => resolve() + done = d + }) + await fun(done) + await p + } +} + +export type Tests = { + [desc: string]: () => Promise +} diff --git a/specs/_run.ts b/specs/_run.ts index f7725bb..13236fc 100644 --- a/specs/_run.ts +++ b/specs/_run.ts @@ -71,6 +71,12 @@ async function run() { } } showResults(start, Date.now()) + + const p = Deno.run({ + cmd: ["bash", "./specs/deno_test.sh"], + }) + + await p.status() } await run() From e8b060766722f71cb7b68b93174eaecec8635fc3 Mon Sep 17 00:00:00 2001 From: Shivam Singla Date: Fri, 12 Jun 2020 23:39:23 +0530 Subject: [PATCH 5/5] add function - andThen https://github.com/Jozty/Fae/issues/26 --- andThen.ts | 14 +++-- specs/andThen.spec.ts | 82 ++++++++++++++++++++------- specs/deno_test.sh | 1 + utils/{assertPromise.ts => assert.ts} | 7 ++- utils/is.ts | 2 +- 5 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 specs/deno_test.sh rename utils/{assertPromise.ts => assert.ts} (54%) diff --git a/andThen.ts b/andThen.ts index 0400fbb..af604b3 100644 --- a/andThen.ts +++ b/andThen.ts @@ -1,11 +1,15 @@ import curryN from "./utils/curry_n.ts" import { Func, Curry2 } from "./utils/types.ts" -import _assertPromise from "./utils/assertPromise.ts" +import { assertPromise } from "./utils/assert.ts" function _andThen(f: Func, p: any) { - _assertPromise('andThen', p); - - return p.then(f); + assertPromise('andThen', p) + return p.then(f) } -export const andThen: Curry2> = curryN(2, _andThen) \ No newline at end of file +/** + * Returns the result of applying the onSuccess function to the value inside + * a successfully resolved promise. This is useful for working with promises + * inside function compositions. + */ +export const andThen: Curry2> = curryN(2, _andThen) diff --git a/specs/andThen.spec.ts b/specs/andThen.spec.ts index 687eca2..562d845 100644 --- a/specs/andThen.spec.ts +++ b/specs/andThen.spec.ts @@ -1,28 +1,68 @@ -import { describe, it } from "./_describe.ts" -import { andThen } from '../mod.ts' -import { eq } from "./utils/utils.ts" +import { it, Tests } from './_async.ts' +import { describe, it as itS} from "./_describe.ts" +import { + andThen, + compose, + pipe, + inc, +} from '../mod.ts' +import { eq, thr } from "./utils/utils.ts" -describe('andThen', () => { - it('invokes then on the promise with the function passed to it', function() { - andThen( - function(n) { - eq(n, 1) - }, - Promise.resolve(1) - ) - }) +const tests: Tests = { + "should invoke then on the promise with the function passed to it": it( + async (done: Function) => { + const p = new Promise(resolve => { + setTimeout(() => { + resolve(1) + }, 100) + }) + andThen( + function (n) { + eq(n, 1) + done() + }, + p + ) + } + ), - it('is not dependent on a particular promise implementation', function() { - let thennable = { - then: function(f: any) { - return f(42) - } + "should flatten promise returning functions": it( + async (done: Function) => { + const incAndWrap = compose(Promise.resolve.bind(Promise), inc) + const asyncAddThree = pipe(incAndWrap, andThen(incAndWrap), andThen(incAndWrap)) + + andThen((result) => { + eq(result, 4) + done() + })(asyncAddThree(1)) } + ), - let f = function(n: any) { - eq(n, 42) + "should not dependent on a particular promise implementation": it( + async (done: Function) => { + const thennable = { + then: function(f: Function) { + return f(42) + } + } + + const f = function(n: number) { + eq(n, 42) + done() + } + + andThen(f, thennable) } + ), +} + +describe('andThen', () => { + itS('throws a typeError if the then method does not exist', () => { + thr(() => andThen(inc, 1), '`andThen` expected a Promise, received 1') - andThen(f, thennable) }) -}) \ No newline at end of file +}) + +for(let desc in tests) { + Deno.test(desc, tests[desc]) +} diff --git a/specs/deno_test.sh b/specs/deno_test.sh new file mode 100644 index 0000000..0f6e8c0 --- /dev/null +++ b/specs/deno_test.sh @@ -0,0 +1 @@ +deno test specs/andThen.spec.ts \ No newline at end of file diff --git a/utils/assertPromise.ts b/utils/assert.ts similarity index 54% rename from utils/assertPromise.ts rename to utils/assert.ts index abdcf90..1cf8d0f 100644 --- a/utils/assertPromise.ts +++ b/utils/assert.ts @@ -1,7 +1,10 @@ import { isFunction } from './is.ts' -export default function _assertPromise(name: any, p: any) { +// TODO: (singla-shivam) change toString function + +export function assertPromise(name: string, p: any): p is PromiseLike { if (p == null || !isFunction(p.then)) { throw new TypeError('`' + name + '` expected a Promise, received ' + p.toString()) } -} \ No newline at end of file + return true +} diff --git a/utils/is.ts b/utils/is.ts index b950528..7114c12 100644 --- a/utils/is.ts +++ b/utils/is.ts @@ -61,4 +61,4 @@ export function isNotUndefinedOrNull(x: any) { export function isArguments(x: any) { return is(x,'Arguments') -} \ No newline at end of file +}