diff --git a/jsconfig.json b/jsconfig.json index 4f05d0e..c30033e 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,12 +1,9 @@ { "compilerOptions": { - "module": "es2020", + "module": "node", "target": "es6", "lib": ["esnext", "dom"], "checkJs": true, - "paths": { - "/*": ["./*"] - } }, "exclude": [ "node_modules" diff --git a/src/function.js b/src/function.js index c2e335c..566400d 100644 --- a/src/function.js +++ b/src/function.js @@ -1,6 +1,6 @@ import { curry, curryN } from './curry.js' import { dispatch } from './fantasy-land.js' -import { head, isIterable, iterConcat, iter, tail } from './list.js' +import { head, isIterable, concatIter, iter, tail } from './list.js' /** @import { Fn, Morphism, InferredMorphism, Predicate } from './types.js' */ @@ -13,10 +13,12 @@ export const id = x => x export const concat = dispatch(['fantasy-land/concat', 'concat'], (b, a) => { - if (isIterable(a) || isIterable(b)) { - return iterConcat(iter(a), iter(b)) - } else { + if (Array.isArray(a) && (Array.isArray(b) || !isIterable(b))) { return a.concat(b) + } else if (Array.isArray(b) && (Array.isArray(a) || !isIterable(a))) { + return b.concat(a) + } else { + return concatIter(iter(a), iter(b)) } }) @@ -122,7 +124,7 @@ export const ap = dispatch(['fantasy-land/ap', 'ap'], const args = liftA(a) const xs = fs.reduce((acc, f) => ( - concat(acc, iter(map(f, args))) + concat(acc, map(f, args)) ), []) return [...xs] diff --git a/src/list.js b/src/list.js index b0cf35a..ac9f65b 100644 --- a/src/list.js +++ b/src/list.js @@ -29,7 +29,7 @@ export function* iter(value) { * @param {...(Iterable | Iterator)} iterators * @yields {T} */ -export const iterConcat = function*(...iterators) { +export const concatIter = function*(...iterators) { for (const iter of iterators) { for (const item of Iterator.from(iter)) { yield item diff --git a/test/units/dispatch.js b/test/units/dispatch.js new file mode 100644 index 0000000..6ec5bd5 --- /dev/null +++ b/test/units/dispatch.js @@ -0,0 +1,62 @@ +import { it, assert, assertEq } from 'folktest' +import { dispatch } from '../../src/fantasy-land.js' + +const test = (re, str = '') => re instanceof RegExp ? re.test(str) : new RegExp(re).test(str) + +const assertErr = (f, err) => { + try { + f() + } catch (e) { + if (typeof err === 'string' || err instanceof RegExp) { + assert(test(err, e.message), `${err} did not match ${e.message}`) + } else if (typeof err === 'function') { + assert(e.constructor === err) + } + + return + } + + const expected = err ? `"${err}" ` : "" + assert(false, `expecting error ${expected}but function did not throw`) +} + +const wrong = msg => assert(false, msg) + +export const Dispatch = [ + it('should dispatch to any listed method or fallback', () => { + const a1 = { + ['fantasy-land/test']: x => assertEq(x, 1), + test: () => wrong(`a1['test'] should not have been called`) + } + + const f1 = dispatch(['fantasy-land/test', 'test'], (v, x) => { + wrong(`f1 fallback should not have been called for ${JSON.stringify(x)}`) + }) + + f1(1, a1) + + delete a1['fantasy-land/test'] + + assertErr(() => { + f1(1, a1) + }) + + const f2 = dispatch(['test', 'fantasy-land/test'], (v, x) => { + wrong(`f2 fallback should not have been called for ${JSON.stringify(x)}`) + }) + + const a2 = { + ['fantasy-land/test']: () => wrong(`a2['fantasy-land/test'] should not have been called`), + test: x => assertEq(x, 2) + } + + f2(2, a2) + + const f3 = dispatch(['fantasy-land/test', 'test'], x => { + assertEq(x, 3) + }) + + f3(3, {}) + }) +] + diff --git a/test/units/function.js b/test/units/function.js new file mode 100644 index 0000000..4ef6512 --- /dev/null +++ b/test/units/function.js @@ -0,0 +1,19 @@ +import { it, assert, assertEq } from 'folktest' +import { concat } from '../..//src/function.js' +import { isIterable, iter } from '../..//src/list.js' + +export const Functions = [ + it('concat', () => { + assertEq(concat(1, []), [1]) + assertEq(concat([], 1), [1]) + assertEq(concat([2], [1]), [1, 2]) + const a = concat([2], iter([1])) + assert(isIterable(a), 'concat([2], iter([1])) returned a non-iterable') + assertEq([...a], [1, 2]) + + const b = concat(iter([2]), [1]) + assert(isIterable(b), 'concat(iter([2]), [1]) returned a non-iterable') + assertEq([...b], [1, 2]) + }) +] + diff --git a/test/units/index.js b/test/units/index.js index 6ec5bd5..d7dd892 100644 --- a/test/units/index.js +++ b/test/units/index.js @@ -1,62 +1,3 @@ -import { it, assert, assertEq } from 'folktest' -import { dispatch } from '../../src/fantasy-land.js' - -const test = (re, str = '') => re instanceof RegExp ? re.test(str) : new RegExp(re).test(str) - -const assertErr = (f, err) => { - try { - f() - } catch (e) { - if (typeof err === 'string' || err instanceof RegExp) { - assert(test(err, e.message), `${err} did not match ${e.message}`) - } else if (typeof err === 'function') { - assert(e.constructor === err) - } - - return - } - - const expected = err ? `"${err}" ` : "" - assert(false, `expecting error ${expected}but function did not throw`) -} - -const wrong = msg => assert(false, msg) - -export const Dispatch = [ - it('should dispatch to any listed method or fallback', () => { - const a1 = { - ['fantasy-land/test']: x => assertEq(x, 1), - test: () => wrong(`a1['test'] should not have been called`) - } - - const f1 = dispatch(['fantasy-land/test', 'test'], (v, x) => { - wrong(`f1 fallback should not have been called for ${JSON.stringify(x)}`) - }) - - f1(1, a1) - - delete a1['fantasy-land/test'] - - assertErr(() => { - f1(1, a1) - }) - - const f2 = dispatch(['test', 'fantasy-land/test'], (v, x) => { - wrong(`f2 fallback should not have been called for ${JSON.stringify(x)}`) - }) - - const a2 = { - ['fantasy-land/test']: () => wrong(`a2['fantasy-land/test'] should not have been called`), - test: x => assertEq(x, 2) - } - - f2(2, a2) - - const f3 = dispatch(['fantasy-land/test', 'test'], x => { - assertEq(x, 3) - }) - - f3(3, {}) - }) -] +export * from './dispatch.js' +export * from './function.js'