Skip to content

Commit 82232c1

Browse files
committed
feat: left combinators for effect.ts
This change adds various combinators for dealing with and creating left types within the Effect type. This includes getsSecond, putsSecond, getSecond, putSecond, swap, mapEither, and fail. It also adds testing and documentation for all new combinators. These additions fall under version 3.0.0-rc.2.
1 parent 3ac6ee1 commit 82232c1

File tree

3 files changed

+245
-2
lines changed

3 files changed

+245
-2
lines changed

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@baetheus/fun",
3-
"version": "3.0.0-rc.1",
3+
"version": "3.0.0-rc.2",
44
"exports": {
55
"./applicable": "./applicable.ts",
66
"./array": "./array.ts",

effect.ts

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,30 @@ export function wrap<A, S extends unknown[] = unknown[]>(
238238
return (...s) => P.resolve([E.right(a), ...s]);
239239
}
240240

241+
/**
242+
* Create a failed Effect with the given error value. The error is placed
243+
* in a Left and the input state is passed through unchanged. This is an
244+
* alias for left and is commonly used in contexts where you want to
245+
* emphasize failure.
246+
*
247+
* @example
248+
* ```ts
249+
* import * as Eff from "./effect.ts";
250+
* import * as E from "./either.ts";
251+
*
252+
* const errorEffect = Eff.fail("Something went wrong");
253+
* const result = await errorEffect("state");
254+
* // [E.left("Something went wrong"), "state"]
255+
* ```
256+
*
257+
* @since 3.0.0-rc.2
258+
*/
259+
export function fail<B, S extends unknown[] = unknown[]>(
260+
b: B,
261+
): Effect<S, B, never> {
262+
return (...s) => P.resolve([E.left(b), ...s]);
263+
}
264+
241265
/**
242266
* Create a successful Effect with the given value. This is an alias for wrap
243267
* and is commonly used in contexts where you want to emphasize success.
@@ -279,7 +303,43 @@ export function right<A, S extends unknown[] = unknown[]>(
279303
export function left<B, S extends unknown[] = unknown[]>(
280304
b: B,
281305
): Effect<S, B, never> {
282-
return (...s) => P.resolve([E.left(b), ...s]);
306+
return fail(b);
307+
}
308+
309+
/**
310+
* Swap the success and error values of an Effect. If the Effect contains
311+
* a Right (success), it becomes a Left (error) with the same value. If it
312+
* contains a Left (error), it becomes a Right (success) with the same value.
313+
* The state is passed through unchanged.
314+
*
315+
* @example
316+
* ```ts
317+
* import * as Eff from "./effect.ts";
318+
* import * as E from "./either.ts";
319+
* import { pipe } from "./fn.ts";
320+
*
321+
* const successEffect = Eff.right("success");
322+
* const swappedSuccess = Eff.swap(successEffect);
323+
*
324+
* const result1 = await swappedSuccess("state");
325+
* // [E.left("success"), "state"]
326+
*
327+
* const errorEffect = Eff.left("error");
328+
* const swappedError = Eff.swap(errorEffect);
329+
*
330+
* const result2 = await swappedError("state");
331+
* // [E.right("error"), "state"]
332+
* ```
333+
*
334+
* @since 3.0.0-rc.2
335+
*/
336+
export function swap<S extends unknown[], A, B, O extends unknown[]>(
337+
ua: Effect<S, B, A, O>,
338+
): Effect<S, A, B, O> {
339+
return async (...s) => {
340+
const [ea, ...o] = await ua.apply(ua, s);
341+
return [E.swap(ea), ...o];
342+
};
283343
}
284344

285345
/**
@@ -391,6 +451,16 @@ export function mapSecond<B, J>(
391451
};
392452
}
393453

454+
export function mapEither<B, A, I, J>(
455+
fee: (ea: Either<B, A>) => Either<J, I>,
456+
): <S extends unknown[], O extends unknown[]>(
457+
ua: Effect<S, B, A, O>,
458+
) => Effect<S, J, I, O> {
459+
return (ua) => async (...s) => {
460+
const [ea, ...o] = await ua.apply(ua, s);
461+
return [fee(ea), ...o];
462+
};
463+
}
394464
/**
395465
* Apply a function wrapped in an Effect to a value wrapped in an Effect.
396466
* Both Effects must succeed for the application to succeed. The function
@@ -595,6 +665,28 @@ export function get<S extends unknown[]>(): Effect<S, never, S, S> {
595665
return (...s) => Promise.resolve([E.right(s), ...s]);
596666
}
597667

668+
/**
669+
* Get the current state as the error value of an Effect. This is the
670+
* dual of `get` - instead of returning the state as a success value
671+
* (Right), it returns the state as an error value (Left). The state
672+
* is both the error value and passed through unchanged.
673+
*
674+
* @example
675+
* ```ts
676+
* import * as Eff from "./effect.ts";
677+
* import * as E from "./either.ts";
678+
*
679+
* const getErrorState = Eff.getSecond<[string]>();
680+
* const result = await getErrorState("hello");
681+
* // [E.left(["hello"]), "hello"]
682+
* ```
683+
*
684+
* @since 3.0.0-rc.2
685+
*/
686+
export function getSecond<S extends unknown[]>(): Effect<S, S, never, S> {
687+
return (...s) => Promise.resolve([E.left(s), ...s]);
688+
}
689+
598690
/**
599691
* Replace the current state with a new value. The new state becomes
600692
* both the success value and the output state.
@@ -618,6 +710,31 @@ export function put<O extends unknown[], S extends unknown[] = unknown[]>(
618710
return () => Promise.resolve([E.right(o), ...o]);
619711
}
620712

713+
/**
714+
* Replace the current state with a new value as an error. This is the
715+
* dual of `put` - instead of returning the new state as a success value
716+
* (Right), it returns the new state as an error value (Left). The new
717+
* state becomes both the error value and the output state.
718+
*
719+
* @example
720+
* ```ts
721+
* import * as Eff from "./effect.ts";
722+
* import * as E from "./either.ts";
723+
*
724+
* const putErrorState = Eff.putSecond("new error state");
725+
*
726+
* const result = await putErrorState("old state");
727+
* // [E.left(["new error state"]), "new error state"]
728+
* ```
729+
*
730+
* @since 3.0.0-rc.2
731+
*/
732+
export function putSecond<O extends unknown[], S extends unknown[] = unknown[]>(
733+
...o: O
734+
): Effect<S, O, never, O> {
735+
return () => Promise.resolve([E.left(o), ...o]);
736+
}
737+
621738
/**
622739
* Extract a value from the current state using a function, without
623740
* modifying the state. The extracted value becomes the success value.
@@ -645,6 +762,35 @@ export function gets<S extends unknown[], A>(
645762
return async (...s) => [E.right(await fsa.apply(fsa, s)), ...s];
646763
}
647764

765+
/**
766+
* Extract a value from the current state using a function as an error value.
767+
* This is the dual of `gets` - instead of returning the extracted value as
768+
* a success value (Right), it returns the extracted value as an error value
769+
* (Left). The state is passed through unchanged.
770+
*
771+
* @example
772+
* ```ts
773+
* import * as Eff from "./effect.ts";
774+
* import * as E from "./either.ts";
775+
*
776+
* const getDoubledError = Eff.getsSecond((s: number) => s * 2);
777+
* const getAsyncError = Eff.getsSecond((s: number) => Promise.resolve(s * 2));
778+
*
779+
* const result1 = await getDoubledError(21);
780+
* // [E.left(42), 21]
781+
*
782+
* const result2 = await getAsyncError(21);
783+
* // [E.left(42), 21]
784+
* ```
785+
*
786+
* @since 3.0.0-rc.2
787+
*/
788+
export function getsSecond<S extends unknown[], B>(
789+
fsa: (...s: S) => B | Promise<B>,
790+
): Effect<S, B, never, S> {
791+
return async (...s) => [E.left(await fsa.apply(fsa, s)), ...s];
792+
}
793+
648794
/**
649795
* Transform the current state using a function. The transformed value
650796
* becomes both the success value and the new state.
@@ -675,6 +821,38 @@ export function puts<S extends unknown[], O extends unknown[] = S>(
675821
};
676822
}
677823

824+
/**
825+
* Transform the current state using a function as an error value. This is
826+
* the dual of `puts` - instead of returning the transformed state as a
827+
* success value (Right), it returns the transformed state as an error value
828+
* (Left). The transformed value becomes both the error value and the new state.
829+
*
830+
* @example
831+
* ```ts
832+
* import * as Eff from "./effect.ts";
833+
* import * as E from "./either.ts";
834+
*
835+
* const doubleErrorState = Eff.putsSecond((n: number) => [n * 2]);
836+
* const asyncPutsError = Eff.putsSecond((n: number) => Promise.resolve([n * 2]));
837+
*
838+
* const result1 = await doubleErrorState(21);
839+
* // [E.left([42]), 42]
840+
*
841+
* const result2 = await asyncPutsError(21);
842+
* // [E.left([42]), 42]
843+
* ```
844+
*
845+
* @since 3.0.0-rc.2
846+
*/
847+
export function putsSecond<S extends unknown[], O extends unknown[] = S>(
848+
fsa: (...s: S) => O | Promise<O>,
849+
): Effect<S, O, never, O> {
850+
return async (...s) => {
851+
const o = await fsa.apply(fsa, s);
852+
return [E.left(o), ...o];
853+
};
854+
}
855+
678856
/**
679857
* Execute an Effect with the given initial state and return only the
680858
* result (Either), discarding the final state.

testing/effect.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,68 @@ Deno.test("Effect puts with identity function", async () => {
538538
const result = await putsEffect("test");
539539
assertEquals(result, [E.right(["test"]), "test"]);
540540
});
541+
542+
Deno.test("Effect fail", async () => {
543+
const errorEffect = Effect.fail("Something went wrong");
544+
const result = await errorEffect("state");
545+
assertEquals(result, [E.left("Something went wrong"), "state"]);
546+
});
547+
548+
Deno.test("Effect swap with right value", async () => {
549+
const successEffect = Effect.right("success");
550+
const swappedSuccess = Effect.swap(successEffect);
551+
552+
const result = await swappedSuccess("state");
553+
assertEquals(result, [E.left("success"), "state"]);
554+
});
555+
556+
Deno.test("Effect swap with left value", async () => {
557+
const errorEffect = Effect.left("error");
558+
const swappedError = Effect.swap(errorEffect);
559+
560+
const result = await swappedError("state");
561+
assertEquals(result, [E.right("error"), "state"]);
562+
});
563+
564+
Deno.test("Effect getSecond", async () => {
565+
const getErrorState = Effect.getSecond<[string]>();
566+
const result = await getErrorState("hello");
567+
assertEquals(result, [E.left(["hello"]), "hello"]);
568+
});
569+
570+
Deno.test("Effect putSecond", async () => {
571+
const putErrorState = Effect.putSecond("new error state");
572+
573+
const result = await putErrorState("old state");
574+
assertEquals(result, [E.left(["new error state"]), "new error state"]);
575+
});
576+
577+
Deno.test("Effect getsSecond with function", async () => {
578+
const getDoubledError = Effect.getsSecond((s: number) => s * 2);
579+
const result = await getDoubledError(21);
580+
assertEquals(result, [E.left(42), 21]);
581+
});
582+
583+
Deno.test("Effect getsSecond with Promise function", async () => {
584+
const getAsyncError = Effect.getsSecond((s: number) => Promise.resolve(s * 2));
585+
const result = await getAsyncError(21);
586+
assertEquals(result, [E.left(42), 21]);
587+
});
588+
589+
Deno.test("Effect putsSecond with sync function", async () => {
590+
const putsErrorEffect = Effect.putsSecond((n: number) => [n * 2]);
591+
const result = await putsErrorEffect(21);
592+
assertEquals(result, [E.left([42]), 42]);
593+
});
594+
595+
Deno.test("Effect putsSecond with async function", async () => {
596+
const putsAsyncError = Effect.putsSecond((n: number) => Promise.resolve([n * 2]));
597+
const result = await putsAsyncError(21);
598+
assertEquals(result, [E.left([42]), 42]);
599+
});
600+
601+
Deno.test("Effect putsSecond with identity function", async () => {
602+
const putsErrorEffect = Effect.putsSecond((s: string) => [s]);
603+
const result = await putsErrorEffect("test");
604+
assertEquals(result, [E.left(["test"]), "test"]);
605+
});

0 commit comments

Comments
 (0)