Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement mask and reveal #217

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
export * from './lambda/index.js';
export * from './processor/index.js';
export * from './standard/index.js';

export * as compare from './compare/index.js';
export * as random from './random/index.js';
export * as dt from './temporal/index.js';

export { default as curry } from './lambda/curry.js';
export { default as pipe } from './lambda/pipe.js';
export { default as unique } from './standard/unique.js';
23 changes: 18 additions & 5 deletions src/core/lambda/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { suite } from 'uvu';
import * as assert from 'uvu/assert';

import curry from './curry.js';
import pipe from './pipe.js';
import * as lambda from './index.js';

const suites = {
'curry/': suite('lambda/curry'),
'pipe/': suite('lambda/pipe'),
'masked/reveal': suite('lambda/masked:reveal'),
};

suites['curry/']('properly curry a function', () => {
const sum = (a: number, b: number, c: number) => a + b + c;
const curried = curry(sum);
const curried = lambda.curry(sum);

assert.type(curried, 'function');
assert.type(curried(1), 'function');
Expand All @@ -25,8 +24,22 @@ suites['pipe/']('properly apply functions in ltr order', () => {
const name = <T extends { name: string }>(v: T) => v.name;
const split = (v: string) => v.split('');

const pipeline = pipe(name, cap, split);
const pipeline = lambda.pipe(name, cap, split);
assert.equal(pipeline({ name: 'mom' }), ['M', 'O', 'M']);
});

suites['masked/reveal']('properly mask and reveal a value', () => {
const { mask, reveal } = lambda;

const answer = mask.of(() => 42);
assert.equal(reveal(answer).expect('unreachable'), 42);

let maybe: string | null | undefined;
let wrapped = mask.wrap(maybe);
assert.equal(reveal(wrapped).or('2023-04-04'), '2023-04-04');

wrapped = mask.wrap('2023-04-06');
assert.equal(reveal(wrapped).expect('unreachable'), '2023-04-06');
});

Object.values(suites).forEach((v) => v.run());
50 changes: 50 additions & 0 deletions src/core/lambda/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export { default as curry } from './curry.js';
export { default as pipe } from './pipe.js';

function error(msg?: string) {
const error = msg || '';
return { kind: 'error' as const, error };
}
function success<T>(value: T) {
return { kind: 'success' as const, value };
}

type Result<T> = ReturnType<typeof error> | ReturnType<typeof success<T>>;

function cast<X, Y extends X>(fn: (x: X) => x is Y) {
return (arg: X): Result<Y> => {
try {
return fn(arg) ? success(arg) : error();
} catch {
return error();
}
};
}

export const mask = {
of<T>(fn: () => T): Result<T> {
try {
return success(fn());
} catch {
return error();
}
},

async resolve<T>(p: Promise<T>): Promise<Result<T>> {
return p.then((v) => success(v)).catch(() => error());
},

wrap: cast(<T>(i: T | undefined | null): i is T => i != null),
} as const;

export function reveal<T>(opt: Result<T>) {
return {
expect(message: string) {
if (opt.kind === 'success') return opt.value;
throw new Error(message);
},
or(fallback: T): T {
return opt.kind === 'success' ? opt.value : fallback;
},
};
}