diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec3b6f0a..bf7963fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - make Array a HKT and deprecate `to`,`from` helper functions, fix #5 (@gcanti) - add `Traced` comonad (@bumbleblym) - add `getOrElse` method to `Option` (@gcanti) + - add NonEmptyArray, fix #12 (@gcanti) - **Polish** - add tslint diff --git a/package.json b/package.json index 8d75e1a63..30d6e4a62 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "typings": "lib/index.d.ts", "scripts": { "lint": "tslint src/**/*.ts test/**/*.ts", - "test": "npm run lint && mocha -r ts-node/register test/*.ts", + "mocha": "mocha -r ts-node/register test/*.ts", + "test": "npm run lint && npm run mocha", "build": "rm -rf lib/* && tsc && tsc -m es6 --outDir lib-jsnext" }, "repository": { diff --git a/src/NonEmptyArray.ts b/src/NonEmptyArray.ts new file mode 100644 index 000000000..c18f4908e --- /dev/null +++ b/src/NonEmptyArray.ts @@ -0,0 +1,89 @@ +import { HKT } from './HKT' +import { Monad } from './Monad' +import { Semigroup } from './Semigroup' +import { Foldable } from './Foldable' +import { Applicative } from './Applicative' +import { Traversable } from './Traversable' +import { Function1, Function2 } from './function' +import * as arr from './Arr' +import { Option, some, none } from './Option' + +export type URI = 'NonEmptyArray' + +export type HKTNonEmptyArray = HKT + +export class NonEmptyArray implements HKTNonEmptyArray { + __hkt: URI // tslint:disable-line variable-name + __hkta: A // tslint:disable-line variable-name + constructor(public head: A, public tail: Array) {} + toArray(): Array { + return [this.head].concat(this.tail) + } + concatArray(as: Array): NonEmptyArray { + return new NonEmptyArray(this.head, this.tail.concat(as)) + } + map(f: Function1): NonEmptyArray { + return new NonEmptyArray(f(this.head), this.tail.map(f)) + } + ap(fab: NonEmptyArray>): NonEmptyArray { + return fab.chain(f => map(f, this)) // <= derived + } + chain(f: Function1>): NonEmptyArray { + return f(this.head).concatArray(arr.chain(a => f(a).toArray(), this.tail)) + } + concat(y: NonEmptyArray): NonEmptyArray { + return this.concatArray(y.toArray()) + } + reduce(f: Function2, b: B): B { + return arr.reduce(f, b, this.toArray()) + } + traverse(applicative: Applicative, f: Function1>): HKT> { + return applicative.map(as => unsafeFromArray(as), arr.traverse(applicative, f, this.toArray())) + } +} + +function unsafeFromArray(as: Array): NonEmptyArray { + return new NonEmptyArray(as[0], as.slice(1)) +} + +export function fromArray(as: Array): Option> { + return as.length ? some(unsafeFromArray(as)) : none +} + +export function map(f: Function1, fa: HKTNonEmptyArray): NonEmptyArray { + return (fa as NonEmptyArray).map(f) +} + +export function ap(fab: HKTNonEmptyArray>, fa: HKTNonEmptyArray): NonEmptyArray { + return (fa as NonEmptyArray).ap((fab as NonEmptyArray>)) +} + +export function of(a: A): HKTNonEmptyArray { + return new NonEmptyArray(a, []) +} + +export function chain(f: Function1>, fa: HKTNonEmptyArray): NonEmptyArray { + return (fa as NonEmptyArray).chain(f as Function1>) +} + +export function concat(fx: HKTNonEmptyArray, fy: HKTNonEmptyArray): NonEmptyArray { + return (fx as NonEmptyArray).concat(fy as NonEmptyArray) +} + +export function reduce(f: Function2, b: B, fa: HKTNonEmptyArray): B { + return (fa as NonEmptyArray).reduce(f, b) +} + +export function traverse(applicative: Applicative, f: Function1>, ta: HKTNonEmptyArray): HKT> { + return (ta as NonEmptyArray).traverse(applicative, f) +} + +// tslint:disable-next-line no-unused-expression +;( + { map, of, ap, chain, concat, reduce, traverse } as ( + Monad & + Semigroup & + Foldable & + Traversable + ) +) diff --git a/test/NonEmptyArray.ts b/test/NonEmptyArray.ts new file mode 100644 index 000000000..ef66e6fb6 --- /dev/null +++ b/test/NonEmptyArray.ts @@ -0,0 +1,29 @@ +import * as assert from 'assert' +import { + NonEmptyArray, + concat, + map, + chain +} from '../src/NonEmptyArray' + +describe('NonEmptyArray', () => { + + it('concat', () => { + const x = new NonEmptyArray(1, [2]) + const y = new NonEmptyArray(3, [4]) + assert.deepEqual(concat(x, y).toArray(), [1, 2, 3, 4]) + }) + + it('map', () => { + const x = new NonEmptyArray(1, [2]) + const double = (n: number) => n * 2 + assert.deepEqual(map(double, x).toArray(), [2, 4]) + }) + + it('chain', () => { + const x = new NonEmptyArray(1, [2]) + const f = (a: number) => new NonEmptyArray(a, [4]) + assert.deepEqual(chain(f, x).toArray(), [1, 4, 2, 4]) + }) + +})