Skip to content

Commit

Permalink
feat: fp many features
Browse files Browse the repository at this point in the history
  • Loading branch information
Hfutsora committed May 26, 2022
1 parent addee2b commit 68d3da0
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 182 deletions.
35 changes: 34 additions & 1 deletion src/Either.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Lazy } from './function'
import { Predicate } from './Predicate'
import { Maybe, none, some } from './Maybe'

export interface Left<E> {
readonly _tag: 'Left'
Expand Down Expand Up @@ -50,6 +52,12 @@ export const map = <A, B>(f: (a: A) => B) => <E>(ma: Either<E, A>) => isLeft(ma)
*/
export const of = right

/**
* instance `alt` operation.
*/
export const alt = <E2, B>(that: Lazy<Either<E2, B>>) => <E1, A>(ma: Either<E1, A>): Either<E2, A | B> =>
isLeft(ma) ? that() : ma

/**
* Returns `Left` or `Right` based on the given predicate.
*
Expand Down Expand Up @@ -84,7 +92,6 @@ export function fromPredicate<A, E>(predicate: Predicate<A>, onFalse: () => E):
export const match = <E, B, A, C>(onLeft: (e: E) => B, onRight: (a: A) => C) => (ma: Either<E, A>): B | C =>
isLeft(ma) ? onLeft(ma.left) : onRight(ma.right)


/**
* Returns the `Either` value if it's a `Right` or a default `onLeft` result value if it's a `Left`.
*
Expand All @@ -98,6 +105,32 @@ export const match = <E, B, A, C>(onLeft: (e: E) => B, onRight: (a: A) => C) =>
export const getOrElse = <E, B>(onLeft: (e: E) => B) => <A>(ma: Either<E, A>): A | B =>
isLeft(ma) ? onLeft(ma.left) : ma.right

/**
* Returns the `Left` value of an `Either` if possible.
*
* @example
*
* ```ts
* assert.deepStrictEqual(getLeft(right(1)), none)
* assert.deepStrictEqual(getLeft(left(1)), some(1))
* ```
*/
export const getLeft = <E, A>(ma: Either<E, A>): Maybe<E> =>
isRight(ma) ? none : some(ma.left)

/**
* Returns the `Left` value of an `Either` if possible.
*
* @example
*
* ```ts
* assert.deepStrictEqual(getRight(right(1)), some(1))
* assert.deepStrictEqual(getRight(left(1)), none)
* ```
*/
export const getRight = <E, A>(ma: Either<E, A>): Maybe<A> =>
isRight(ma) ? some(ma.right) : none

/**
* Composes computations in sequence.
*
Expand Down
85 changes: 57 additions & 28 deletions src/Iterator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { flow } from './function'
import { Predicate } from './Predicate'


/**
Expand All @@ -16,6 +17,7 @@ import { flow } from './function'
export const isEmpty = <A>(ma: Iterable<A>): boolean => {
if ('length' in ma) return (ma as any).length == 0
if ('size' in ma) return (ma as any).size == 0
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _ of ma) return false

return true
Expand Down Expand Up @@ -51,7 +53,11 @@ export function* unshift<T>(a: Iterable<T>, ...items: T[]): Iterable<T> {
/**
* instance `of` operation.
*/
export const of = <A>(...as: A[]): Seq<A> => seq(as)
export function* of<A>(...as: A[]): Iterable<A> {
for(const a of as) {
yield a
}
}

/**
* instance `map` operation.
Expand Down Expand Up @@ -187,6 +193,29 @@ export const count = <A>(ma: Iterable<A>): number => {
return collect(ma).length
}

/**
* Returns an iterator that elements meet the condition specified in a predicate function.
*
* @example
*
* ```ts
* assert.deepStrictEqual(
* pipe(
* [1, 2, 3, 4],
* filter(a => a % 2 === 0)
* ),
* [2, 4]
* )
* ```
*/
export const filter = <A>(predicate: Predicate<A>) => function* (ma: Iterable<A>): Iterable<A> {
for(const a of ma) {
if(predicate(a)) {
yield a
}
}
}

/**
* Takes two iterator and returns an iterator of the function results. If one iterator is short, excess items of the longer iterator are discarded.
*
Expand Down Expand Up @@ -271,7 +300,7 @@ export function* flatten<A>(ma: Iterable<Iterable<A>>): Iterable<A> {
}

/**
* Same as [`Seq`](#chain), but passing also the index to the iterating function.
* Same as [`Iter`](#chain), but passing also the index to the iterating function.
*
* @example
*
Expand All @@ -294,51 +323,51 @@ export const chain = <A, B>(f: (a: A) => Iterable<B>) => (ma: Iterable<A>): Iter


/**
* Creates a `Seq` from an iterator.
* Creates a `Iter` from an iterator.
*
* @example
*
* ```ts
* assert.deepStrictEqual(seq([1]), new Seq(() => [1]))
* assert.deepStrictEqual(iter([1]), new Iter(() => [1]))
* ```
*/
export const seq = <A>(ma: Iterable<A>): Seq<A> => new Seq(() => ma)
export const iter = <A>(ma: Iterable<A>): Iter<A> => new Iter(() => ma)

/**
* Seq
* Iter
*/
export class Seq<A> implements Iterable<A> {
constructor(public readonly iter: () => Iterable<A>) {}
export class Iter<A> implements Iterable<A> {
constructor(public readonly _iter: () => Iterable<A>) {}

[Symbol.iterator](): Iterator<A> {
return this.iter()[Symbol.iterator]()
return this._iter()[Symbol.iterator]()
}

get arr() {
return this.toArray()
}

static of = of
static to = flow(to, seq)
static range = flow(range, seq)
static makeBy = flow(makeBy, seq)
static replicate = flow(replicate, seq)

map = <B>(f: (a: A) => B) => flow(map(f), seq)(this.iter())
toArray = (): A[] => toArray(this.iter())
isEmpty= (): boolean => isEmpty(this.iter())
push = (...as: A[]): Seq<A> => flow(push, seq)(this.iter(), ...as)
unshift = (...as: A[]) => flow(unshift, seq)(this.iter(), ...as)
collect = (): A[] => collect(this.iter())
join = (seperator?: string) => flow(join(seperator))(this.iter())
count = () => count(this.iter())
zipWith = <B, C>(b: Iterable<B>, f: (a: A, b: B) => C) => flow(zipWith, seq)(this.iter(), b, f)
zip = <B>(b: Iterable<B>) => flow(zip, seq)(this.iter(), b)
unzip = (): Seq<[
static of = flow(of, iter)
static to = flow(to, iter)
static range = flow(range, iter)
static makeBy = flow(makeBy, iter)
static replicate = flow(replicate, iter)

map = <B>(f: (a: A) => B) => flow(map(f), iter)(this._iter())
toArray = (): A[] => toArray(this._iter())
isEmpty= (): boolean => isEmpty(this._iter())
push = (...as: A[]): Iter<A> => flow(push, iter)(this._iter(), ...as)
unshift = (...as: A[]) => flow(unshift, iter)(this._iter(), ...as)
collect = (): A[] => collect(this._iter())
join = (seperator?: string) => flow(join(seperator))(this._iter())
count = () => count(this._iter())
zipWith = <B, C>(b: Iterable<B>, f: (a: A, b: B) => C) => flow(zipWith, iter)(this._iter(), b, f)
zip = <B>(b: Iterable<B>) => flow(zip, iter)(this._iter(), b)
unzip = (): Iter<[
(A extends [infer X, any] | readonly [infer X, any] | (infer X)[] ? X : unknown)[],
(A extends [any, infer Y] | readonly [any, infer Y] | (infer Y)[] ? Y : unknown)[],
]> => flow(unzip, seq)(this.iter() as any) as any
flatten = (): A extends Iterable<infer R> ? Seq<R> : never => flow(flatten, seq)(this.iter() as any) as any
]> => flow(unzip, iter)(this._iter() as any) as any
flatten = (): A extends Iterable<infer R> ? Iter<R> : never => flow(flatten, iter)(this._iter() as any) as any
}


34 changes: 32 additions & 2 deletions src/Maybe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,48 @@ export const map = <A, B>(f: (a: A) => B) => (fa: Maybe<A>): Maybe<B> =>
isNone(fa) ? none : some(f(fa.value))

/**
* instance `of` operation
* instance `of` operation.
*/
export const of = some

/**
* instance `alt` operation.
*/
export const alt = <B>(that: Lazy<Maybe<B>>) => <A>(ma: Maybe< A>): Maybe<A | B> =>
isNone(ma) ? that() : ma

/**
* instance `ap` operation.
*/
export const ap = <A, B>(f: (a: A) => B) => (ma: Maybe<A>): Maybe<B> =>
isNone(ma) ? none : some(f(ma.value))


/**
* Returns the callback function result, if the `Maybe` is `Some`, otherwise returns undefined.
*
* @example
*
* ```ts
* const f = flow(
* then((n: number) => n + 1)
* )
*
* assert.deepStrictEqual(f(some(1)), 2)
* assert.deepStrictEqual(f(none), undefined)
* ```
*/
export const then = <A, B>(f: (a: A) => B) => (ma: Maybe<A>): B | undefined =>
isNone(ma) ? constUndefined() : f(ma.value)

/**
* Returns the onNone default value if the `Maybe` is `None`, otherwise returns the onSome function result with `Maybe`.
*
* @example
*
* ```ts
* const f = flow(
* match(() => 0, (n: number) => n + 1)
* match(() => 0, (n: number) => n + 1)
* )
*
* assert.deepStrictEqual(f(some(1)), 2)
Expand Down
7 changes: 2 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import * as either from './Either'
import * as function_ from './function'
import * as maybe from './Maybe'
import * as iter from './Iterator'


export { seq, Seq } from './Iterator'
import * as iterator from './Iterator'

export {
either,
function_,
maybe,
iter
iterator
}
29 changes: 27 additions & 2 deletions test/Either.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { isLeft, left, right, isRight, map, of, fromPredicate, match, getOrElse, chain, orElse, exists } from '../src/Either'
import { isLeft, left, right, isRight, map, of, fromPredicate, match, getOrElse, chain, orElse, exists, alt, getLeft, getRight } from '../src/Either'
import { flow } from '../src/function'
import { none, some } from '../src/Maybe'

test('isLeft', () => {
expect(isLeft(left(0))).toBeTruthy()
Expand All @@ -21,14 +23,27 @@ test('of', () => {
expect(of(1)).toEqual(right(1))
})

test('alt', () => {
const f = flow(
alt(() => right(1))
)
expect(f(right(2))).toEqual(right(2))
expect(f(left(2))).toEqual(right(1))

const f2 = flow(
alt(() => left(1))
)
expect(f2(right(2))).toEqual(right(2))
expect(f2(left(2))).toEqual(left(1))
})

test('fromPredicate', () => {
const f = fromPredicate((n: number) => n > 0, () => 'error')

expect(f(1)).toEqual(right(1))
expect(f(-1)).toEqual(left('error'))
})


test('match', () => {
const f = match((n: number) => n - 1, (n: number) => n + 1)
expect(f(left(1))).toBe(0)
Expand All @@ -40,6 +55,16 @@ test('getOrElse', () => {
expect(getOrElse(() => 0)(right(1))).toBe(1)
})

test('getLeft', () => {
expect(getLeft(right(1))).toEqual(none)
expect(getLeft(left(1))).toEqual(some(1))
})

test('getRight', () => {
expect(getRight(right(1))).toEqual(some(1))
expect(getRight(left(1))).toEqual(none)
})

test('chain', () => {
expect(chain((n: number) => right(n + 1))(left(1))).toEqual(left(1))
expect(chain((n: number) => right(n + 1))(right(1))).toEqual(right(2))
Expand Down
Loading

0 comments on commit 68d3da0

Please sign in to comment.