Skip to content

Commit

Permalink
test: Add tests for Apply (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina authored Aug 5, 2024
1 parent 00568a4 commit 7045b03
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 3 deletions.
6 changes: 3 additions & 3 deletions src/ordering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
87 changes: 87 additions & 0 deletions src/type-class/apply.test.ts
Original file line number Diff line number Diff line change
@@ -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<Option.OptionHkt>;

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),
);
}
});
58 changes: 58 additions & 0 deletions src/type-class/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<S> extends Functor<S> {
/**
* 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: <T, U>(
fn: Get1<S, (t: T) => U>,
) => (t: Get1<S, T>) => Get1<S, U>;
}

/**
* 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 =
<SA, SB>(applyA: Apply<SA>, applyB: Apply<SB>) =>
<T>(funcT: Get1<SA, Get1<SB, T>>) =>
<U>(funcM: Get1<SA, Get1<SB, (t: T) => U>>): Get1<SA, Get1<SB, U>> =>
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 =
<S>(apply: Apply<S>) =>
<T>(first: Get1<S, T>): <U>(second: Get1<S, U>) => Get1<S, T> =>
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 =
<S>(apply: Apply<S>) =>
<T>(first: Get1<S, T>): <U>(second: Get1<S, U>) => Get1<S, U> =>
Expand All @@ -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 =
<S>(apply: Apply<S>) =>
<N extends PropertyKey, T>(name: Exclude<N, keyof T>) =>
Expand All @@ -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 = <F>(app: Apply<F>) =>
<A, B, C>(
f: (a: A) => (b: B) => C,
): (fa: Get1<F, A>) => (fb: Get1<F, B>) => Get1<F, C> =>
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 =
<S>(apply: Apply<S>) => <T>(semi: SemiGroup<T>): SemiGroup<Get1<S, T>> => ({
combine: (l, r) =>
Expand Down

0 comments on commit 7045b03

Please sign in to comment.