Skip to content

Commit

Permalink
minor
Browse files Browse the repository at this point in the history
ReaderEither & new Either utils
  • Loading branch information
VKTRenokh committed Apr 9, 2024
2 parents 104ab38 + 4a87cc8 commit 1f19aae
Show file tree
Hide file tree
Showing 25 changed files with 277 additions and 61 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,11 @@
# duck-fp

## 1.12.0

### Minor Changes

- 4a87cc8: Added ReaderEither, Either fromPredicate & fromPredicateC utils, Either ensureOrElse overload

## 1.11.6

### Patch Changes
Expand Down
1 change: 1 addition & 0 deletions jest.config.ts
Expand Up @@ -4,6 +4,7 @@ const config: Config.InitialOptions = {
preset: 'ts-jest',
verbose: true,
testEnvironment: 'node',
maxWorkers: 4,
moduleNameMapper: {
'->/(.*)': '<rootDir>/src/$1',
'-->': '<rootDir>/src/index.ts',
Expand Down
47 changes: 6 additions & 41 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "duck-fp",
"version": "1.11.6",
"version": "1.12.0",
"description": "Maybe, either, state, reader, readerT and identity in typescript",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down Expand Up @@ -93,6 +93,11 @@
"require": "./dist/functions/index.js",
"types": "./dist/functions/index.d.ts"
},
"./reader-either": {
"import": "./dist/reader-either/index.mjs",
"require": "./dist/reader-either/index.js",
"types": "./dist/reader-either/index.d.ts"
},
"./types": {
"types": "./dist/types/index.d.ts"
}
Expand Down Expand Up @@ -149,6 +154,6 @@
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tsc-alias": "^1.8.8",
"typescript": "^5.4.3"
"typescript": "^5.4.4"
}
}
32 changes: 20 additions & 12 deletions src/either/either.ts
@@ -1,4 +1,6 @@
import { Merged } from '->t/merged'
import { Predicate } from '->t/predicate'
import { Refinement } from '->t/refinement'

// {{{ types for either functions
export type FoldFunction<Left, Right> = <R>(
Expand All @@ -22,10 +24,14 @@ export type FlatMapFunction<Left, Right> = <R>(
fn: (v: Right) => Either<Left, R>,
) => Either<Left, R>

export type EnsureOrElseFunction<Left, Right> = (
p: (v: Right) => boolean,
fr: (v: Right) => Left,
) => Either<Left, Right>
interface EnsureOrElse<R> {
<E, T extends R>(
p: Refinement<R, T>,
c: (v: R) => E,
): Either<E, T>

<E>(p: Predicate<R>, c: (v: R) => E): Either<E, R>
}

export type MergeFunction<Left, Right> = <Nr>(
or: Either<Left, Nr>,
Expand Down Expand Up @@ -105,7 +111,7 @@ export interface Left<T, R> {
* @param {function(R): Re} fr The function to compute an alternative failure value if the predicate fails.
* @returns {Either<L, R>} An `Either` instance with the same failure type `L` but potentially different success type.
*/
ensureOrElse: EnsureOrElseFunction<T, R>
ensureOrElse: EnsureOrElse<R>

/**
* Merges two Either monads into one, combining their values if both are Right, or returning the first Left value otherwise.
Expand Down Expand Up @@ -149,7 +155,7 @@ export interface Right<T, R> {

flatMap: FlatMapFunction<R, T>

ensureOrElse: EnsureOrElseFunction<R, T>
ensureOrElse: EnsureOrElse<T>

merge: MergeFunction<R, T>
}
Expand Down Expand Up @@ -188,8 +194,8 @@ export const left = <L, R = never>(e: L): Either<L, R> => ({
flatMap: <Re>(_: (v: R) => Either<L, Re>) =>
left<L, Re>(e),

ensureOrElse: (_: (v: R) => boolean, __: (v: R) => L) =>
left(e),
ensureOrElse: ((_: (v: R) => boolean, __: (v: R) => L) =>
left(e)) as EnsureOrElse<R>,

merge: <Nl, Nr, R>(_: Either<Nl, Nr>) => left<L, R>(e),

Expand Down Expand Up @@ -231,10 +237,10 @@ export const right = <R, L = never>(

flatMap: <Re>(fn: (v: R) => Either<L, Re>) => fn(v),

ensureOrElse: <Re>(
ensureOrElse: (<Re>(
fn: (v: R) => boolean,
fr: (v: R) => Re,
) => (fn(v) ? right(v) : left(fr(v))),
) => (fn(v) ? right(v) : left(fr(v)))) as EnsureOrElse<R>,

merge: <Nr>(
or: Either<L, Nr>,
Expand All @@ -260,8 +266,10 @@ export const right = <R, L = never>(
export const of = <Right, Left>(value: Right) =>
right<Right, Left>(value)

export type GetRight<T extends Either<any, any>> =
// {{{ Infers
export type GetRight<T extends Either<unknown, unknown>> =
T extends Either<infer _, infer U> ? U : never

export type GetLeft<T extends Either<any, any>> =
export type GetLeft<T extends Either<unknown, unknown>> =
T extends Either<infer U, infer _> ? U : never
// }}}
29 changes: 29 additions & 0 deletions src/either/utils/from-predicate-c.ts
@@ -0,0 +1,29 @@
import { Either, left, right } from '../either'

/**
* Creates a function similar to {@link ./from-predicate.ts} but curried for better composition.
* This function returns a new function that takes a value of type `T` and applies
* the provided predicate function. If the predicate succeeds, it returns a right
* containing the original value. If the predicate fails, it returns a left containing
* the result of the provided error function `c`.
*
* @param predicate The predicate function to determine if the value
* should result in a right or left.
* @param c The function returning the error value in case the predicate fails.
* @returns A curried function taking a value of type `T` and returning
* an Either, with a right containing the original value if the predicate succeeds, or a left
* containing the error value if the predicate fails.
*
* @example
* const predicator = fromPredicateC(
* (number: number) => number > 10 && number < 15,
* (n) => `${n} is greater than 15 or lower than 10.`,
* )
*
* predicator(11) // Right containing 11
* predicator(100) // Left containing `100 is greater than 15 or lowe than 10`
*/
export const fromPredicateC =
<T, E>(predicate: (v: T) => boolean, c: (v: T) => E) =>
(v: T): Either<E, T> =>
predicate(v) ? right(v) : left(c(v))
32 changes: 32 additions & 0 deletions src/either/utils/from-predicate.ts
@@ -0,0 +1,32 @@
import { Either, left, right } from '../either'

/**
* Creates an Either based on the result of a predicate function.
* If the predicate function returns true for the provided value, returns a right
* containing the original value. If the predicate function returns false,
* returns a left containing the result of the provided error function.
*
* @template T The type of the value being evaluated.
* @template E The type of the error value.
* @param {T} v The value to be evaluated by the predicate function.
* @param {function(T): boolean} predicate The predicate function to determine if the value
* should result in a right or left.
* @param {function(T): E} c The function returning the error value in case the predicate fails.
* @returns {Either<E, T>} An Either instance, with a right containing the original value
* if the predicate succeeds, or a left containing the error value if the predicate fails.
*
* @example
* // Example usage:
* const either = fromPredicate(
* 10,
* (num) => num < 2,
* (v) => `${v} is greater than 2.`,
* );
*
* either.fold(console.error, console.log); // Outputs: "10 is greater than 2."
*/
export const fromPredicate = <T, E>(
v: T,
predicate: (v: T) => boolean,
c: (v: T) => E,
): Either<E, T> => (predicate(v) ? right(v) : left(c(v)))
2 changes: 2 additions & 0 deletions src/either/utils/index.ts
Expand Up @@ -5,3 +5,5 @@ export * from './merge'
export * from './try-catch'
export * from './is'
export * from './to-error'
export * from './from-predicate'
export * from './from-predicate-c'
1 change: 1 addition & 0 deletions src/functions/index.ts
@@ -1 +1,2 @@
export * from './multiple'
export * from './side'
6 changes: 6 additions & 0 deletions src/functions/side.ts
@@ -0,0 +1,6 @@
/**
* helps to debug many .map calls
*/
export const side =
<T>(f: (v: T) => void) =>
(v: T) => (f(v), v)
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -11,5 +11,6 @@ export * as T from './task'
export * as TE from './task-either'
export * as TM from './task-maybe'
export * as RM from './reader-maybe'
export * as RE from './reader-either'
export * as F from './functions'
export * as TY from './types'
1 change: 1 addition & 0 deletions src/maybe/maybe.ts
Expand Up @@ -63,6 +63,7 @@ export interface Maybe<T> {

/**
* Asynchronously maps over the value contained in the Maybe monad.
* @deprecated use `TaskEither` instead
* @template R - The type of the result after applying the mapping function.
* @param {(v: T) => Promise<R>} fn - The async mapping function.
* @param {(err: unknown) => void} [error] - Optional error handler.
Expand Down
2 changes: 2 additions & 0 deletions src/reader-either/index.ts
@@ -0,0 +1,2 @@
export * from './reader-either'
export * from './utils'
24 changes: 24 additions & 0 deletions src/reader-either/reader-either.ts
@@ -0,0 +1,24 @@
import { Either, isRight, left } from '->/either'

export interface ReaderEither<E, L, R> {
run: (e: E) => Either<L, R>
map: <Re>(f: (v: R) => Re) => ReaderEither<E, L, Re>
flatMap: <Re>(
f: (v: R) => ReaderEither<E, L, Re>,
) => ReaderEither<E, L, Re>
}

export const of = <E, L, R>(
run: (e: E) => Either<L, R>,
): ReaderEither<E, L, R> => ({
run,
map: <Re>(f: (v: R) => Re): ReaderEither<E, L, Re> =>
of((e) => run(e).map(f)),
flatMap: <Re>(
f: (v: R) => ReaderEither<E, L, Re>,
): ReaderEither<E, L, Re> =>
of((e) => {
const a = run(e)
return isRight(a) ? f(a.right).run(e) : left(a.left)
}),
})
1 change: 1 addition & 0 deletions src/reader-either/utils/index.ts
@@ -0,0 +1 @@
export * from './try-catch'
14 changes: 14 additions & 0 deletions src/reader-either/utils/try-catch.ts
@@ -0,0 +1,14 @@
import { left, right } from '->/either'
import { ReaderEither, of } from '->/reader-either'

export const tryCatch = <E, T, M>(
tryFn: (e: E) => T,
catchFn: (e: unknown) => M,
): ReaderEither<E, M, T> =>
of((e) => {
try {
return right(tryFn(e))
} catch (err) {
return left(catchFn(err))
}
})
14 changes: 10 additions & 4 deletions src/task-either/task-either.ts
Expand Up @@ -4,18 +4,24 @@ import {
right as eitherRight,
isRight,
} from '->/either'
import { Predicate, Refinement } from '->/types'
import { LazyPromise } from '->/types/lazy-promise'

// {{{ TaskEither interface
export interface EnsureOrElse<R> {
<E, T extends R>(
p: Refinement<R, T>,
c: (v: R) => E,
): TaskEither<E, T>
<E>(p: Predicate<R>, c: (v: R) => E): TaskEither<E, R>
}

export interface TaskEither<Left, Right> {
map: <R>(f: (v: Right) => R) => TaskEither<Left, R>
flatMap: <R>(
f: (v: Right) => TaskEither<Left, R>,
) => TaskEither<Left, R>
ensureOrElse: (
p: (v: Right) => boolean,
fr: (v: Right) => Left,
) => TaskEither<Left, Right>
ensureOrElse: EnsureOrElse<Right>
orElse: <R>(
f: (e: Left) => TaskEither<R, Right>,
) => TaskEither<R, Right>
Expand Down
3 changes: 3 additions & 0 deletions src/task/task.ts
Expand Up @@ -37,5 +37,8 @@ export const of = <T>(run: LazyPromise<T>): Task<T> => ({
run,
})

export const run = <T>(v: T): Task<T> =>
of(() => Promise.resolve(v))

export type UnwrapTask<T extends Task<unknown>> =
T extends Task<infer U> ? U : never
2 changes: 2 additions & 0 deletions src/types/index.ts
Expand Up @@ -2,3 +2,5 @@ export * from './lazy'
export * from './merged'
export * from './abstract-fn'
export * from './lazy-promise'
export * from './predicate'
export * from './refinement'
3 changes: 3 additions & 0 deletions src/types/predicate.ts
@@ -0,0 +1,3 @@
export interface Predicate<T> {
(v: T): boolean
}
3 changes: 3 additions & 0 deletions src/types/refinement.ts
@@ -0,0 +1,3 @@
export interface Refinement<T, B extends T> {
(v: T): v is B
}

0 comments on commit 1f19aae

Please sign in to comment.