While doing functional programming I often end up in situations where I know something that the type system of the language does not know. Consider the following TypeScript example that parses a UUID and shows the embedded fields to the user. The program first validates it's input with io-ts to make sure the input follows UUID specification. Later, after splitting the input, the program is unable to verify that the split UUID contains five parts which leaves me with an fp-ts Option
. It throws an assert false
from getOrElse
to get rid of the Option
. Does functional programming have some more idiomatic ways to deal with assertions? Reporting the error to the end user doesn't feel helpful since this case would be an error in underlying assumptions of the programmer rather than something that the end user could solve.
#!/usr/bin/env ts-node
import { append, intersperse, map, prepend } from 'fp-ts/lib/Array';
import { isRight } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/function';
import { IO } from 'fp-ts/lib/IO';
import { fromPredicate, getOrElse } from 'fp-ts/lib/Option';
import { empty } from 'fp-ts/lib/string';
import * as t from 'io-ts';
type Tuple5<A, B, C, D, E> = [A, B, C, D, E];
const length = 5;
const fromArray = fromPredicate(
<A>(as: Array<A>): as is Tuple5<A, A, A, A, A> => as.length === length,
);
const Tuple5_ = {
length,
fromArray,
};
const separator = '-';
const hex = (n: number): string => `[A-Fa-f0-9]{${n}}`;
const fields: Tuple5<number, number, number, number, number> = [8, 4, 4, 4, 12];
const regexp = pipe(
fields,
map(hex),
intersperse(separator),
prepend('^'),
append('$'),
).join(empty);
export type Uuid = t.Branded<string, UuidBrand>;
export type UuidC = t.BrandC<t.StringC, UuidBrand>;
export const Uuid: UuidC = t.brand(
t.string,
(x): x is t.Branded<string, UuidBrand> => x.match(RegExp(regexp)) !== null,
'Uuid',
);
export type UuidBrand = {
readonly Uuid: unique symbol;
};
export type TimeLow = string;
export type TimeMid = string;
export type TimeHiAndVersion = string;
export type ClockSeq = string;
export type Node = string;
export type Groups = Tuple5<TimeLow, TimeMid, TimeHiAndVersion, ClockSeq, Node>;
export const groups = (uuid: Uuid): Groups =>
pipe(
uuid.split(separator),
Tuple5_.fromArray,
getOrElse((): Groups => {
// eslint-disable-next-line
throw new Error('Assert false! Uuid invalid despite validation.');
}),
);
const main: IO<void> = () => {
const [_node, _script, input] = process.argv;
const result = Uuid.decode(input);
if (isRight(result)) {
const uuid: Uuid = result.right;
const [timeLow, timeMid, timeHiAndVersion, clockSeq, node] = groups(uuid);
console.log({ timeLow, timeMid, timeHiAndVersion, clockSeq, node });
} else {
console.error('Invalid input!');
}
};
main();