Open MKRhere opened 4 years ago
While res.ok
doesn't guard msg
, it does guard res.value
.
I'd add cata
method to the type returned by validate
, for easy conversion to more sophisticated types, and for "inline" branching, without introducing a constant like res
:
// result.ts
interface CataOk<OkIn, OkOut> {
Ok(value: OkIn): OkOut
}
interface CataErr<ErrIn, ErrOut> {
Err(err: ErrIn): ErrOut
}
type Cata<OkIn, OkOut, ErrIn, ErrOut> = CataOk<OkIn, OkOut> & CataErr<ErrIn, ErrOut>
interface BaseResult<T, E> {
readonly ok: boolean
cata<OkOut, ErrOut>(cata: Cata<T, OkOut, E, ErrOut>): OkOut | ErrOut
}
export class Ok<T> implements BaseResult<T, never> {
readonly ok = true as const
constructor(readonly value: T) {}
cata<OkOut>(cata: CataOk<T, OkOut>) {
return cata.Ok(this.value)
}
}
export class Err<E> implements BaseResult<never, E> {
readonly ok = false as const
constructor(readonly error: E) {}
cata<ErrOut>(cata: CataErr<E, ErrOut>) {
return cata.Err(this.error)
}
}
export type Result<T, E> = BaseResult<T, E> & (Ok<T> | Err<E>)
ValidationError
might be confused with Err
, perhaps they should be merged?ValidationError
be subclassed for representing different kinds of validation errors?positiveNumber
).Ok
, should it require or disallow Err
"clause"?type AnyFn = (...args: any[]) => any;
interface Cata<Ok extends AnyFn, Err extends AnyFn> {
Ok: Ok,
Err: Err
}
class Ok<Value> {
constructor(private value: Value) {}
cata<
Ok extends (value: Value) => any,
Err extends AnyFn
>(x: Cata<Ok, Err>): ReturnType<Ok> {
return x.Ok(this.value);
}
}
class Err<Error> {
constructor(private error: Error) {}
cata<
Ok extends AnyFn,
Err extends (error: Error) => any
>(x: Cata<Ok, Err>): ReturnType<Err> {
return x.Err(this.error);
}
}
export class Result<Value, Error> {
static Ok<Value, Error = never>(value: Value) {
return new Result<Value, Error>(new Ok(value));
}
static Err<Error, Value = never>(error: Error) {
return new Result<Value, Error>(new Err(error));
}
constructor(private result: Ok<Value> | Err<Error>) {}
cata<
Ok extends (value: Value) => any,
Err extends (error: Error) => any
>(x: Cata<Ok, Err>): ReturnType<Ok> | ReturnType<Err> {
return (this.result.cata as AnyFn)(x);
}
map<
NextValue
>(mapper: (value: Value) => NextValue): Result<NextValue, Error> {
return this.bimap<NextValue, Error>(mapper, <T>(x: T) => x);
}
mapErr<
NextError
>(mapper: (error: Error) => NextError): Result<Value, NextError> {
return this.bimap<Value, NextError>(<T>(x: T) => x, mapper);
}
bimap<
NextValue,
NextError
>(
mapValue: (value: Value) => NextValue,
mapError: (error: Error) => NextError
): Result<NextValue, NextError> {
return new Result((this.result.cata as AnyFn)({
Ok: (value: Value) => new Ok(mapValue(value)),
Err: (error: Error) => new Err(mapError(error))
}));
}
}
Here's an alternative implementation of Result
With @trgwii's suggestions:
type AnyFn = (x: never) => unknown;
interface Cata<OkFn extends AnyFn, ErrFn extends AnyFn> {
Ok: OkFn
Err: ErrFn
}
type CataOk<OkFn extends AnyFn> = Cata<OkFn, AnyFn>;
type CataErr<ErrFn extends AnyFn> = Cata<AnyFn, ErrFn>;
interface BaseResult<T, E> {
readonly ok: boolean
cata<Value, Error>(cata: Cata<(x: T) => Value, (x: E) => Error>): Value | Error
}
export namespace Result {
export class Ok<T> implements BaseResult<T, never> {
readonly ok = true as const;
constructor(readonly value: T) { }
cata<Value>(cata: CataOk<(value: T) => Value>) {
return cata.Ok(this.value);
}
}
export class Err<E> implements BaseResult<never, E> {
readonly ok = false as const;
constructor(readonly error: E) { }
cata<Error>(cata: CataErr<(error: E) => Error>) {
return cata.Err(this.error);
}
}
}
export type Result<T, E> = BaseResult<T, E> & (Result.Ok<T> | Result.Err<E>);
As we know, runtype only tells you if an type-check passed or not, and doesn't tell why it failed. This proposal outlines the new API for validation messages.
Description
Currently,
Predicate
is a function that takesx
and guards it against a type. This proposal recommends the addition of avalidate
method toPredicate
which returns the following type:Where
ValidationError
extendsError
with the propertiesexpected
andactual
describing the corresponding types as string to produce a nicer error; and by default.message
describing a generated error.Example:
Downsides:
res.ok
doesn't actually guardmsg
, and you get no static types like you normally would with the type guard.