Skip to content

Commit

Permalink
Merge pull request #24 from ENvironmentSet/add-fp-ts-monad-instance
Browse files Browse the repository at this point in the history
Make `Effectful` to be monad
  • Loading branch information
ENvironmentSet authored Aug 25, 2023
2 parents 37c953e + 1aab595 commit efe2171
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [`hyogwa/core`](./core.md)
- [`hyogwa/runners`](./runners.md)
- [`hyogwa/monad`](./monad.md)
- [`hyogwa/assistants`](./assistants.md)
- [`hyogwa/async-task`](./async-task.md)
- [`hyogwa/state`](./state.md)
Expand Down
35 changes: 35 additions & 0 deletions docs/api/monad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `hyogwa/monad`

Module implementing functions needed for `Effectful` to be monad. Utilize functions here to define monad instance of
`Effectful` when using other functional programming libraries like `fp-ts` and `fun`.

> **⚠️ CAUTION**: Strictly speaking, `Effectful` is not a monad. Evaluating the same effectful computation multiple times
> will produce unsound result. Have your code run(via `yield*`, `handle` or runners.) effectful computations only once per instance.
## `map<E, A, B>(f: (a: A) => B, computation: Effectful<E, A>): Effectful<E, B>`

Applies function to result of a computation.

## `map<A, B>(f: (a: A) => B): <E>(computation: Effectful<E, A>) => Effectful<E, B>`

Lifts given function to work with effectful computations instead of plain values

## `of<T>(value: T): Effectful<never, T>`

Makes computation of the given value

## `ap<E, A, B>(wrappedF: Effectful<E, (a: A) => B>, computation: Effectful<E, A>): Effectful<E, B>`

Applies function inside effectful computation to result of another computation.

## `ap<E1, A, B>(wrappedF: Effectful<E1, (a: A) => B>): <E2>(computation: Effectful<E2, A>) => Effectful<E1 | E2, B>`

Unwrap function inside effectful computation as function for effectful computations.

## `chain<E, A, B>(f: (a: A) => Effectful<E, B>, computation: Effectful<E, A>): Effectful<E, B>`

Applies effectful function to effectful computation.

## `chain<E1, A, B>(f: (a: A) => Effectful<E1, B>): <E2>(computation: Effectful<E2, A>) => Effectful<E1 | E2, B>`

Lifts effectful function to be function of effectful computations.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"./env": "./src/env.ts",
"./state": "./src/state.ts",
"./log": "./src/log.ts",
"./async-task": "./src/async-task.ts"
"./async-task": "./src/async-task.ts",
"./monad": "./src/monad.ts"
},
"sideEffects": false,
"scripts": {
Expand Down
105 changes: 105 additions & 0 deletions src/monad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Effectful } from './core';

/**
* Applies function to result of a computation
*
* @beta
*
* @param f - A function to apply
* @param computation - A computation whose result will be applied
* @returns A computation mapped by `f`
*/
export function map<E, A, B>(f: (a: A) => B, computation: Effectful<E, A>): Effectful<E, B>
/**
* Lifts given function to work with effectful computations instead of plain values
*
* @beta
*
* @param f - A function to lift
* @returns lifted function
*/
export function map<A, B>(f: (a: A) => B): <E>(computation: Effectful<E, A>) => Effectful<E, B>
export function map<E, A, B>(f: (a: A) => B, computation?: Effectful<E, A>)
: Effectful<E, B> | (<E>(computation: Effectful<E, A>) => Effectful<E, B>) {
if (!computation) return function* (computation) { return f(yield* computation) }
else return (function* () { return f(yield* computation) })()
}

/**
* Makes computation of the given value
*
* @beta
*
* @param value
* @returns pure computation only producing the given value
*/
export function* of<T>(value: T): Effectful<never, T> {
return value
}

/**
* Applies function inside effectful computation to result of another computation
*
* @beta
*
* @param wrappedF - An effectful computation wrapping a function
* @param computation - An effectful computation whose result will be applied
*/
export function ap<E, A, B>(wrappedF: Effectful<E, (a: A) => B>, computation: Effectful<E, A>): Effectful<E, B>
/**
* Unwrap function inside effectful computation as function for effectful computations
*
* @beta
*
* @param wrappedF - A function to unwrap
* @returns an unwrapped function for effectful computations
*/
export function ap<E1, A, B>(wrappedF: Effectful<E1, (a: A) => B>)
: <E2>(computation: Effectful<E2, A>) => Effectful<E1 | E2, B>
export function ap<E, A, B>(wrappedF: Effectful<E, (a: A) => B>, computation?: Effectful<E, A>)
: Effectful<E, B> | (<E2>(computation: Effectful<E2, A>) => Effectful<E | E2, B>) {
if (!computation) return function* (computation) {
const f = yield* wrappedF
const a = yield* computation

return f(a)
}
else return (function* () {
const f = yield* wrappedF
const a = yield* computation

return f(a)
})()
}

/**
* Applies effectful function to effectful computation
*
* @beta
*
* @param f - An effectful function to apply
* @param computation - An effectful computation to be applied
*/
export function chain<E, A, B>(f: (a: A) => Effectful<E, B>, computation: Effectful<E, A>): Effectful<E, B>
/**
* Lifts effectful function to be function of effectful computations
*
* @beta
*
* @param f - An effectful function to lift
*/
export function chain<E1, A, B>(f: (a: A) => Effectful<E1, B>)
: <E2>(computation: Effectful<E2, A>) => Effectful<E1 | E2, B>
export function chain<E, A, B>(f: (a: A) => Effectful<E, B>, computation?: Effectful<E, A>)
: Effectful<E, B> | (<E2>(computation: Effectful<E2, A>) => Effectful<E | E2, B>) {
if (!computation) return function* (computation) {
const a = yield* computation

return yield* f(a)
}
else return (function* () {
const a = yield* computation

return yield* f(a)
})()
}

0 comments on commit efe2171

Please sign in to comment.