Skip to content

Commit

Permalink
Add NonEmptyArray, fix #12 (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Feb 28, 2017
1 parent f9c97e8 commit 82c367b
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- make Array<T> 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

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
89 changes: 89 additions & 0 deletions src/NonEmptyArray.ts
Original file line number Diff line number Diff line change
@@ -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<A> = HKT<URI, A>

export class NonEmptyArray<A> implements HKTNonEmptyArray<A> {
__hkt: URI // tslint:disable-line variable-name
__hkta: A // tslint:disable-line variable-name
constructor(public head: A, public tail: Array<A>) {}
toArray(): Array<A> {
return [this.head].concat(this.tail)
}
concatArray(as: Array<A>): NonEmptyArray<A> {
return new NonEmptyArray(this.head, this.tail.concat(as))
}
map<B>(f: Function1<A, B>): NonEmptyArray<B> {
return new NonEmptyArray(f(this.head), this.tail.map(f))
}
ap<B>(fab: NonEmptyArray<Function1<A, B>>): NonEmptyArray<B> {
return fab.chain(f => map(f, this)) // <= derived
}
chain<B>(f: Function1<A, NonEmptyArray<B>>): NonEmptyArray<B> {
return f(this.head).concatArray(arr.chain(a => f(a).toArray(), this.tail))
}
concat(y: NonEmptyArray<A>): NonEmptyArray<A> {
return this.concatArray(y.toArray())
}
reduce<B>(f: Function2<B, A, B>, b: B): B {
return arr.reduce(f, b, this.toArray())
}
traverse<F, B>(applicative: Applicative<F>, f: Function1<A, HKT<F, B>>): HKT<F, NonEmptyArray<B>> {
return applicative.map(as => unsafeFromArray(as), arr.traverse(applicative, f, this.toArray()))
}
}

function unsafeFromArray<A>(as: Array<A>): NonEmptyArray<A> {
return new NonEmptyArray(as[0], as.slice(1))
}

export function fromArray<A>(as: Array<A>): Option<NonEmptyArray<A>> {
return as.length ? some(unsafeFromArray(as)) : none
}

export function map<A, B>(f: Function1<A, B>, fa: HKTNonEmptyArray<A>): NonEmptyArray<B> {
return (fa as NonEmptyArray<A>).map(f)
}

export function ap<A, B>(fab: HKTNonEmptyArray<Function1<A, B>>, fa: HKTNonEmptyArray<A>): NonEmptyArray<B> {
return (fa as NonEmptyArray<A>).ap((fab as NonEmptyArray<Function1<A, B>>))
}

export function of<A>(a: A): HKTNonEmptyArray<A> {
return new NonEmptyArray(a, [])
}

export function chain<A, B>(f: Function1<A, HKTNonEmptyArray<B>>, fa: HKTNonEmptyArray<A>): NonEmptyArray<B> {
return (fa as NonEmptyArray<A>).chain(f as Function1<A, NonEmptyArray<B>>)
}

export function concat<A>(fx: HKTNonEmptyArray<A>, fy: HKTNonEmptyArray<A>): NonEmptyArray<A> {
return (fx as NonEmptyArray<A>).concat(fy as NonEmptyArray<A>)
}

export function reduce<A, B>(f: Function2<B, A, B>, b: B, fa: HKTNonEmptyArray<A>): B {
return (fa as NonEmptyArray<A>).reduce(f, b)
}

export function traverse<F, A, B>(applicative: Applicative<F>, f: Function1<A, HKT<F, B>>, ta: HKTNonEmptyArray<A>): HKT<F, NonEmptyArray<B>> {
return (ta as NonEmptyArray<A>).traverse(applicative, f)
}

// tslint:disable-next-line no-unused-expression
;(
{ map, of, ap, chain, concat, reduce, traverse } as (
Monad<URI> &
Semigroup<any> &
Foldable<URI> &
Traversable<URI>
)
)
29 changes: 29 additions & 0 deletions test/NonEmptyArray.ts
Original file line number Diff line number Diff line change
@@ -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])
})

})

0 comments on commit 82c367b

Please sign in to comment.