From 7045b0385669c4ed35b8810c60a6f7cccbb7d1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikuro=E3=81=95=E3=81=84=E3=81=AA?= Date: Mon, 5 Aug 2024 11:37:47 +0900 Subject: [PATCH] test: Add tests for Apply (#255) --- src/ordering.ts | 6 +-- src/type-class/apply.test.ts | 87 ++++++++++++++++++++++++++++++++++++ src/type-class/apply.ts | 58 ++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 src/type-class/apply.test.ts diff --git a/src/ordering.ts b/src/ordering.ts index 181d437c..dab1f6c6 100644 --- a/src/ordering.ts +++ b/src/ordering.ts @@ -12,17 +12,17 @@ import { semiGroupSymbol } from "./type-class/semi-group.ts"; /** * Means that the left term is less than the right term. */ -export const less = -1; +export const less = -1 as const; export type Less = typeof less; /** * Means that the left term equals to the right term. */ -export const equal = 0; +export const equal = 0 as const; export type Equal = typeof equal; /** * Means that the left term is greater than the right term. */ -export const greater = 1; +export const greater = 1 as const; export type Greater = typeof greater; /** * The ordering about two terms. diff --git a/src/type-class/apply.test.ts b/src/type-class/apply.test.ts new file mode 100644 index 00000000..a006779f --- /dev/null +++ b/src/type-class/apply.test.ts @@ -0,0 +1,87 @@ +import { + ap, + apFirst, + type Apply, + apSecond, + apSelective, + makeSemiGroup, + map2, +} from "./apply.ts"; +import { Option, Ordering } from "../../mod.ts"; +import { assertEquals } from "../../deps.ts"; + +const apply = Option.applicative as Apply; + +Deno.test("ap", () => { + assertEquals( + ap(apply, apply)(Option.some(Option.some(3)))( + Option.some(Option.some((x: number) => x * 2)), + ), + Option.some(Option.some(6)), + ); +}); +Deno.test("apFirst", () => { + assertEquals( + apFirst(apply)(Option.some(3))(Option.some(4)), + Option.some(3), + ); + assertEquals(apFirst(apply)(Option.some(3))(Option.none()), Option.none()); + assertEquals(apFirst(apply)(Option.none())(Option.some(4)), Option.none()); + assertEquals(apFirst(apply)(Option.none())(Option.none()), Option.none()); +}); +Deno.test("apSecond", () => { + assertEquals( + apSecond(apply)(Option.some(3))(Option.some(4)), + Option.some(4), + ); + assertEquals(apSecond(apply)(Option.some(3))(Option.none()), Option.none()); + assertEquals(apSecond(apply)(Option.none())(Option.some(4)), Option.none()); + assertEquals(apSecond(apply)(Option.none())(Option.none()), Option.none()); +}); +Deno.test("apSelective", () => { + assertEquals( + apSelective(apply)("key")(Option.some({ x: 5 }))(Option.some("foo")), + Option.some({ x: 5, key: "foo" }), + ); + assertEquals( + apSelective(apply)("key")(Option.some({ x: 5 }))(Option.none()), + Option.none(), + ); + assertEquals( + apSelective(apply)("key")(Option.none())(Option.some("foo")), + Option.none(), + ); + assertEquals( + apSelective(apply)("key")(Option.none())(Option.none()), + Option.none(), + ); +}); +Deno.test("map2", () => { + const lifted = map2(apply)((x: string) => (y: string) => y + x); + + assertEquals( + lifted(Option.some("foo"))(Option.some("bar")), + Option.some("barfoo"), + ); + assertEquals(lifted(Option.none())(Option.some("bar")), Option.none()); + assertEquals(lifted(Option.some("foo"))(Option.none()), Option.none()); + assertEquals(lifted(Option.none())(Option.none()), Option.none()); +}); +Deno.test("makeSemiGroup", () => { + const m = makeSemiGroup(apply)(Ordering.monoid); + + for (const x of [Ordering.less, Ordering.equal, Ordering.greater]) { + assertEquals( + m.combine(Option.some(Ordering.equal), Option.some(x)), + Option.some(x), + ); + assertEquals( + m.combine(Option.some(Ordering.less), Option.some(x)), + Option.some(Ordering.less), + ); + assertEquals( + m.combine(Option.some(Ordering.greater), Option.some(x)), + Option.some(Ordering.greater), + ); + } +}); diff --git a/src/type-class/apply.ts b/src/type-class/apply.ts index 21865c62..50953b7a 100644 --- a/src/type-class/apply.ts +++ b/src/type-class/apply.ts @@ -3,23 +3,58 @@ import type { Get1 } from "../hkt.ts"; import type { Functor } from "./functor.ts"; import { type SemiGroup, semiGroupSymbol } from "./semi-group.ts"; +/** + * A structure which able to evaluate a function over `S`. + */ export interface Apply extends Functor { + /** + * Applies the function to the value over `S`. + * + * @param fn - The wrapped function. + * @param t - The wrapped value. + * @returns The value got by evaluating `fn`. + */ readonly apply: ( fn: Get1 U>, ) => (t: Get1) => Get1; } +/** + * Sequences two computations over two functors. + * + * @param applyA - The `Apply` instance for `SA`. + * @param applyB - The `Apply` instance for `SB`. + * @param first - The first computation to be sequenced. + * @param second - The second computation to be sequenced. + * @returns The sequenced computation, doing `first` then `second`. + */ export const ap = (applyA: Apply, applyB: Apply) => (funcT: Get1>) => (funcM: Get1 U>>): Get1> => applyA.apply(applyA.map(applyB.apply)(funcM))(funcT); +/** + * Sequences two computations, discarding the result of `second`. + * + * @param apply - The `Apply` instance for `S`. + * @param first - The first computation to be sequenced. + * @param second - The second computation to be sequenced. + * @returns The sequenced computation, doing `first` then `second`. + */ export const apFirst = (apply: Apply) => (first: Get1): (second: Get1) => Get1 => apply.apply(apply.map((t: T) => () => t)(first)); +/** + * Sequences two computations, discarding the result of `first`. + * + * @param apply - The `Apply` instance for `S`. + * @param first - The first computation to be sequenced. + * @param second - The second computation to be sequenced. + * @returns The sequenced computation, doing `first` then `second`. + */ export const apSecond = (apply: Apply) => (first: Get1): (second: Get1) => Get1 => @@ -29,6 +64,15 @@ export const apSecond = )(first), ); +/** + * Sequences two computations, composing the results of them. + * + * @param apply - The `Apply` instance for `S`. + * @param name - The object key to pick up. + * @param funcT - The computation resulting `T`. + * @param funcU - The computation resulting `U`. + * @returns The composed computation resulting object `T` with an entry of type `U` by `name` key. + */ export const apSelective = (apply: Apply) => (name: Exclude) => @@ -46,12 +90,26 @@ export const apSelective = )(funcT), ); +/** + * Lifts up the two-parameter function over `F`. + * + * @param app - The `Apply` instance for `F`. + * @param f - The function which takes two parameters. + * @returns The function lifted over `F`. + */ export const map2 = (app: Apply) => ( f: (a: A) => (b: B) => C, ): (fa: Get1) => (fb: Get1) => Get1 => pipe(app.map(f))(app.apply); +/** + * Lifts up the semi-group instance over the apply functor. + * + * @param apply - The `Apply` instance for `S`. + * @param semi - The `SemiGroup` instance for `T`. + * @returns The lifted `SemiGroup` instance. + */ export const makeSemiGroup = (apply: Apply) => (semi: SemiGroup): SemiGroup> => ({ combine: (l, r) =>