fp-ts
fp-ts copied to clipboard
Either.Do: `left` to be structurally similar to `right`
I've come across certain challenge with Either.DO
Here's a working example
import * as A from 'fp-ts/Apply'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
import * as S from 'fp-ts/Semigroup'
import * as string from 'fp-ts/string'
import * as RR from 'fp-ts/ReadonlyRecord';
const parseString = (u: unknown): E.Either<string, string> =>
typeof u === 'string' ? E.right(u) : E.left('not a string')
const parseNumber = (u: unknown): E.Either<string, number> =>
typeof u === 'number' ? E.right(u) : E.left('not a number')
interface Person {
readonly name: string
readonly age: number
}
(() => {
const apS = A.apS(E.getApplicativeValidation(RR.getUnionSemigroup(pipe(string.Semigroup, S.intercalate(', ')))))
const lift = <E, A, B, C extends string>(action: (a: B) => E.Either<E, A>, name: C): (a: B) => E.Either<RR.ReadonlyRecord<string, E>, A> => (a) => pipe(action(a), E.mapLeft((e) => ({ [name]: e })));
const parsePerson = (input: Record<string, unknown>) =>
pipe(
E.Do,
apS('name',lift(parseString, "name" as const)(input.name)),
apS('age', lift(parseNumber, "age" as const)(input.age))
)
console.log(parsePerson({}))
})();
So, the return type of parsePerson(...)
is
const parsePerson: (input: Record<string, unknown>) => E.Either<Readonly<Record<string, string>>, {
readonly name: string;
readonly age: number;
}>
...whille I'm trying to make it look like this
const parsePerson: (input: Record<string, unknown>) => E.Either<
readonly name: string;
readonly age: string;
>, {
readonly name: string;
readonly age: number;
}>
You can't get that result from that input because it doesn't encode for fallibility, for example what if there's no name
key at all?