如何在 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()

我的问题:

  1. 有什么办法吗?
  2. 有没有办法用自定义编解码器扩展 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

(顺便说一句,上面的RsummonFor的通用参数(例如IAppEnv)。)

“原始”的 modelIoTs(Non)StrictInterpreters 在这里定义:

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 部分。

这与 modelEqInterpretermodelShowInterpreter 非常相似,除了分别使用 EqTypeShowType

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, {})),

@morphic-ts/show-interpreters/src/model/primitives.ts