如何在 morphic-ts 召唤器中注入 io-ts 类型?
How to inject io-ts-types in morphic-ts summoner?
举个例子:
import * as Summoner from '@morphic-ts/batteries/lib/summoner-ESBST';
export interface IAppEnv {}
const { summon, tagged } = summonFor<IAppEnv>({});
const ReplyEntry_ = summon((F) =>
F.interface(
{
subject: F.string(),
action: F.string(),
},
'ReplyEntry'
)
);
我想使用 io-ts-types
模块中的 NumberFromString
而不是 F.string()
。
我的问题:
- 有什么办法吗?
- 有没有办法用自定义编解码器扩展
F
对象?
我知道我可以使用 io-ts-types
中的一些编解码器,方法是像这样改进它们:
const NES = summon(F => F.refined(F.string(), NonEmptyString.is, 'NES'))
也许有一种方法可以使用 F.newtype
。
我可以在 https://github.com/sledorze/morphic-ts/blob/master/packages/morphic-io-ts-interpreters/test/io-ts-interpreter.spec.ts#L97
中看到示例
interface NT extends Newtype<{ readonly NT: unique symbol }, Date> {}
const NT = summon(F => F.newtype<NT>('NT')(F.date()))
但是它好像没有任何类型转换逻辑,我不知道放在哪里。
我不熟悉这个库,所以可能有更简洁、更简单的方法来做这件事,但在我查看了 @morphic-ts
的代码后,我想到了这个:
import { summonFor } from '@morphic-ts/batteries/lib/summoner-ESBST';
import { EqType, EqURI } from '@morphic-ts/eq-interpreters';
import { IOTSType, IoTsURI } from '@morphic-ts/io-ts-interpreters';
import { ShowType, ShowURI } from '@morphic-ts/show-interpreters';
import * as Num from 'fp-ts/Number';
import { NumberFromString } from 'io-ts-types';
import type { AlgebraUnion } from '@morphic-ts/batteries/lib/program';
import type { M } from '@morphic-ts/batteries/lib/summoner-ESBST';
import type { HKT, Kind, URIS } from '@morphic-ts/common/lib/HKT';
import type { AnyEnv } from '@morphic-ts/common/lib/config';
import type { Eq } from 'fp-ts/Eq';
import type { Show } from 'fp-ts/Show';
import type { Type } from 'io-ts';
const customIoTs = <A, O>(
type: Type<A, O>,
eq: Eq<A>,
show: Show<A>
): (<Env extends AnyEnv>(F: AlgebraUnion<'HKT', Env>) => HKT<Env, O, A>) => {
const ioTsInterp: Kind<IoTsURI, AnyEnv, O, A> = () => new IOTSType(type);
const eqInterp: Kind<EqURI, AnyEnv, O, A> = () => new EqType(eq);
const showInterp: Kind<ShowURI, AnyEnv, O, A> = () => new ShowType(show);
return (<Env extends AnyEnv>(
F: AlgebraUnion<URIS, Env>
): Kind<URIS, Env, O, A> =>
F._F === IoTsURI
? ioTsInterp
: F._F === EqURI
? eqInterp
: F._F === ShowURI
? showInterp
: ((): never => {
throw new Error(`Unknown URI: ${F._F}`);
})()) as <Env extends AnyEnv>(
F: AlgebraUnion<'HKT', Env>
) => HKT<Env, O, A>;
};
interface IAppEnv {}
const { summon } = summonFor<IAppEnv>({});
const numberFromString = customIoTs(NumberFromString, Num.Eq, Num.Show);
// Usage
const Foo = summon(F => numberFromString(F));
Foo.type.decode('123'); // Right 123
Foo.type.decode('abc'); // Left [...]
Foo.eq.equals(Foo.build(1), Foo.build(1)); // true
Foo.show.show(Foo.build(1)); // '1'
const Bar = summon(F => F.interface({ num: numberFromString(F) }, 'Bar'));
Bar.strictType.decode({ num: '123' }); // Right { num: 123 }
Bar.strictType.decode({ num: 'abc' }); // Left [...]
Bar.eq.equals(Bar.build({ num: 1 }), Bar.build({ num: 1 })); // true
Bar.show.show(Bar.build({ num: 1 })); // '{ num: 1 }'
说明
ESBST 召唤器使用不同的“解释器”(summon(F => ...)
中的 F
)多次调用您传递给 summon
(program
) 的函数:
const { create, type } = program(modelIoTsNonStrictInterpreter<NonNullable<R>>())(env)
return {
build: a => a,
eq: program(modelEqInterpreter<NonNullable<R>>())(env).eq,
show: program(modelShowInterpreter<NonNullable<R>>())(env).show,
strictType: program(modelIoTsStrictInterpreter<NonNullable<R>>())(env).type,
type,
create
}
@morphic-ts/batteries/src/summoner-ESBST.ts
(顺便说一句,上面的R
是summonFor
的通用参数(例如IAppEnv
)。)
“原始”的 modelIoTs(Non)StrictInterpreter
s 在这里定义:
export const ioTsPrimitiveInterpreter = memo(
<Env extends AnyEnv>(): ModelAlgebraPrimitive<IoTsURI, Env> => ({
_F: IoTsURI,
date: config => env => new IOTSType(iotsApplyConfig(config)(DateFromISOString, env, {})),
boolean: config => env => new IOTSType(iotsApplyConfig(config)(t.boolean, env, {})),
string: config => env => new IOTSType(iotsApplyConfig(config)(t.string, env, {})),
number: config => env => new IOTSType(iotsApplyConfig(config)(t.number, env, {})),
// some more omitted for brevity
@morphic-ts/io-ts-interpreters/src/model/primitives.ts
这里的重要部分是 new IOTSType
。我不认为我们的 NumberFromString
需要任何 configuration,所以我在实现中跳过了 iotsApplyConfig
部分。
这与 modelEqInterpreter
和 modelShowInterpreter
非常相似,除了分别使用 EqType
和 ShowType
:
export const eqPrimitiveInterpreter = memo(
<Env extends AnyEnv>(): ModelAlgebraPrimitive<EqURI, Env> => ({
_F: EqURI,
date: config => env =>
new EqType(
eqApplyConfig(config)(
eq.contramap(eqNumber, (date: Date) => date.getTime()),
env,
{}
)
),
boolean: config => env => new EqType(eqApplyConfig(config)(eqBoolean, env, {})),
string: config => env => new EqType(eqApplyConfig(config)(eqString, env, {})),
number: config => env => new EqType(eqApplyConfig(config)(eqNumber, env, {})),
@morphic-ts/eq-interpreters/src/model/primitives.ts
export const showPrimitiveInterpreter = memo(
<Env extends AnyEnv>(): ModelAlgebraPrimitive<ShowURI, Env> => ({
_F: ShowURI,
date: config => env => new ShowType(showApplyConfig(config)({ show: (date: Date) => date.toISOString() }, env, {})),
boolean: config => env => new ShowType(showApplyConfig(config)(showBoolean, env, {})),
string: config => env => new ShowType(showApplyConfig(config)(showString, env, {})),
number: config => env => new ShowType(showApplyConfig(config)(showNumber, env, {})),
举个例子:
import * as Summoner from '@morphic-ts/batteries/lib/summoner-ESBST';
export interface IAppEnv {}
const { summon, tagged } = summonFor<IAppEnv>({});
const ReplyEntry_ = summon((F) =>
F.interface(
{
subject: F.string(),
action: F.string(),
},
'ReplyEntry'
)
);
我想使用 io-ts-types
模块中的 NumberFromString
而不是 F.string()
。
我的问题:
- 有什么办法吗?
- 有没有办法用自定义编解码器扩展
F
对象?
我知道我可以使用 io-ts-types
中的一些编解码器,方法是像这样改进它们:
const NES = summon(F => F.refined(F.string(), NonEmptyString.is, 'NES'))
也许有一种方法可以使用 F.newtype
。
我可以在 https://github.com/sledorze/morphic-ts/blob/master/packages/morphic-io-ts-interpreters/test/io-ts-interpreter.spec.ts#L97
interface NT extends Newtype<{ readonly NT: unique symbol }, Date> {}
const NT = summon(F => F.newtype<NT>('NT')(F.date()))
但是它好像没有任何类型转换逻辑,我不知道放在哪里。
我不熟悉这个库,所以可能有更简洁、更简单的方法来做这件事,但在我查看了 @morphic-ts
的代码后,我想到了这个:
import { summonFor } from '@morphic-ts/batteries/lib/summoner-ESBST';
import { EqType, EqURI } from '@morphic-ts/eq-interpreters';
import { IOTSType, IoTsURI } from '@morphic-ts/io-ts-interpreters';
import { ShowType, ShowURI } from '@morphic-ts/show-interpreters';
import * as Num from 'fp-ts/Number';
import { NumberFromString } from 'io-ts-types';
import type { AlgebraUnion } from '@morphic-ts/batteries/lib/program';
import type { M } from '@morphic-ts/batteries/lib/summoner-ESBST';
import type { HKT, Kind, URIS } from '@morphic-ts/common/lib/HKT';
import type { AnyEnv } from '@morphic-ts/common/lib/config';
import type { Eq } from 'fp-ts/Eq';
import type { Show } from 'fp-ts/Show';
import type { Type } from 'io-ts';
const customIoTs = <A, O>(
type: Type<A, O>,
eq: Eq<A>,
show: Show<A>
): (<Env extends AnyEnv>(F: AlgebraUnion<'HKT', Env>) => HKT<Env, O, A>) => {
const ioTsInterp: Kind<IoTsURI, AnyEnv, O, A> = () => new IOTSType(type);
const eqInterp: Kind<EqURI, AnyEnv, O, A> = () => new EqType(eq);
const showInterp: Kind<ShowURI, AnyEnv, O, A> = () => new ShowType(show);
return (<Env extends AnyEnv>(
F: AlgebraUnion<URIS, Env>
): Kind<URIS, Env, O, A> =>
F._F === IoTsURI
? ioTsInterp
: F._F === EqURI
? eqInterp
: F._F === ShowURI
? showInterp
: ((): never => {
throw new Error(`Unknown URI: ${F._F}`);
})()) as <Env extends AnyEnv>(
F: AlgebraUnion<'HKT', Env>
) => HKT<Env, O, A>;
};
interface IAppEnv {}
const { summon } = summonFor<IAppEnv>({});
const numberFromString = customIoTs(NumberFromString, Num.Eq, Num.Show);
// Usage
const Foo = summon(F => numberFromString(F));
Foo.type.decode('123'); // Right 123
Foo.type.decode('abc'); // Left [...]
Foo.eq.equals(Foo.build(1), Foo.build(1)); // true
Foo.show.show(Foo.build(1)); // '1'
const Bar = summon(F => F.interface({ num: numberFromString(F) }, 'Bar'));
Bar.strictType.decode({ num: '123' }); // Right { num: 123 }
Bar.strictType.decode({ num: 'abc' }); // Left [...]
Bar.eq.equals(Bar.build({ num: 1 }), Bar.build({ num: 1 })); // true
Bar.show.show(Bar.build({ num: 1 })); // '{ num: 1 }'
说明
ESBST 召唤器使用不同的“解释器”(summon(F => ...)
中的 F
)多次调用您传递给 summon
(program
) 的函数:
const { create, type } = program(modelIoTsNonStrictInterpreter<NonNullable<R>>())(env)
return {
build: a => a,
eq: program(modelEqInterpreter<NonNullable<R>>())(env).eq,
show: program(modelShowInterpreter<NonNullable<R>>())(env).show,
strictType: program(modelIoTsStrictInterpreter<NonNullable<R>>())(env).type,
type,
create
}
@morphic-ts/batteries/src/summoner-ESBST.ts
(顺便说一句,上面的R
是summonFor
的通用参数(例如IAppEnv
)。)
“原始”的 modelIoTs(Non)StrictInterpreter
s 在这里定义:
export const ioTsPrimitiveInterpreter = memo(
<Env extends AnyEnv>(): ModelAlgebraPrimitive<IoTsURI, Env> => ({
_F: IoTsURI,
date: config => env => new IOTSType(iotsApplyConfig(config)(DateFromISOString, env, {})),
boolean: config => env => new IOTSType(iotsApplyConfig(config)(t.boolean, env, {})),
string: config => env => new IOTSType(iotsApplyConfig(config)(t.string, env, {})),
number: config => env => new IOTSType(iotsApplyConfig(config)(t.number, env, {})),
// some more omitted for brevity
@morphic-ts/io-ts-interpreters/src/model/primitives.ts
这里的重要部分是 new IOTSType
。我不认为我们的 NumberFromString
需要任何 configuration,所以我在实现中跳过了 iotsApplyConfig
部分。
这与 modelEqInterpreter
和 modelShowInterpreter
非常相似,除了分别使用 EqType
和 ShowType
:
export const eqPrimitiveInterpreter = memo(
<Env extends AnyEnv>(): ModelAlgebraPrimitive<EqURI, Env> => ({
_F: EqURI,
date: config => env =>
new EqType(
eqApplyConfig(config)(
eq.contramap(eqNumber, (date: Date) => date.getTime()),
env,
{}
)
),
boolean: config => env => new EqType(eqApplyConfig(config)(eqBoolean, env, {})),
string: config => env => new EqType(eqApplyConfig(config)(eqString, env, {})),
number: config => env => new EqType(eqApplyConfig(config)(eqNumber, env, {})),
@morphic-ts/eq-interpreters/src/model/primitives.ts
export const showPrimitiveInterpreter = memo(
<Env extends AnyEnv>(): ModelAlgebraPrimitive<ShowURI, Env> => ({
_F: ShowURI,
date: config => env => new ShowType(showApplyConfig(config)({ show: (date: Date) => date.toISOString() }, env, {})),
boolean: config => env => new ShowType(showApplyConfig(config)(showBoolean, env, {})),
string: config => env => new ShowType(showApplyConfig(config)(showString, env, {})),
number: config => env => new ShowType(showApplyConfig(config)(showNumber, env, {})),