효과 TS 핵심: ZIO Prelude 계발 유형 클래스 및 모듈 구조

100545 단어 typescriptfunctional
첫 번째 글에서 @effect-ts/core에서 사용된 HKT의 독특한 인코딩 배후의 원리를 묘사하였으니 이제 세부 사항을 살펴볼 때가 되었다.
우리는 사용 가능한 Type-Classes 탐색을 시작하여 점차적으로 용법 예시를 제시할 것이다.
마지막으로, 우리는 모듈 구조와 사용할 수 있는 주문에 대해 토론할 것이다.

프로젝트 설정


간단한 새 프로젝트부터 시작하겠습니다 (Scala/Haskell에서 왔을 때만 곤란합니다. TS를 알고 있으면 무시합니다).
mkdir effect-ts-series; 
cd effect-ts-series;
npm init -y;
yarn add typescript@next @effect-ts/core @types/node;
mkdir src;
다음과 같은 파일tsconfig.json을 만듭니다.
{
  "compilerOptions": {
    "strict": true,
    "target": "ES5",
    "outDir": "lib",
    "lib": ["ESNext"]
  },
  "include": ["src/**/*.ts"]
}
다음 내용을 포함하는 파일을 만듭니다src/index.ts.
import * as T from "@effect-ts/core/Effect";
import { pipe } from "@effect-ts/core/Function";

pipe(
  T.effectTotal(() => {
    console.log("Hello world");
  }),
  T.runMain
);
다음과 같이 구축 스크립트를 package.json에 추가합니다.
{
  "name": "effect-ts-series",
  "version": "1.0.0",
  "description": "Effect-TS Series",
  "main": "lib/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node lib/index.js"
  },
  "keywords": [],
  "author": "Michael Arnaldi",
  "license": "MIT",
  "dependencies": {
    "@effect-ts/core": "^0.2.0",
    "@types/node": "^14.11.2",
    "typescript": "^4.1.0-dev.20201004"
  }
}
우리는 이 항목을 다음과 같이 컴파일할 수 있어야 한다.
yarn build
다음 작업을 실행합니다.
$ yarn start
yarn run v1.22.4
$ node lib/index.js
Hello world
Done in 0.46s.

치오 전주곡 유형 과정 안내


우선, 우리는 약간의 이론과 고전적인 유형의 차원 구조를 수정하는 원인부터 시작합시다.
우리가 오늘 알고 있는 정적 유형 함수 프로그래밍은haskell과 그 설계 원칙에 효과적으로 뿌리를 둔다.여러 해 동안 우리는 지역 사회로서 원칙을 하나하나 빌려 다른 언어에 융합시켰다.
기능을 한 언어에서 다른 언어로 이식하는 과정은 쉽지 않다. 여러 가지 절차가 필요하다. 첫 번째 단계는 유사한 인코딩을 찾는 것이고, 두 번째 단계는 기초 위에서 개선하는 것이다.
하스켈의 유형 체계는 범주 이론의 계발을 받았지만 수학적으로 말하자면 그것은 일종의'근사'일 뿐이고 HM 가족 언어에서 의미 있는 이론의 특정 서브집합에 전념한다.우리는 이 이론의 나머지 부분을 소홀히 해서는 안 된다. 특히 개념을 다른 언어로 확장할 때haskell에서 한 같은 가설이 우리 언어에 적용되지 않을 수 있기 때문이다. (예를 들어 처리 중인 모든 함수)
ZIO Prelude는 함수 프로그래밍 개념을 Scala로 추상화하고 개편하는 두 번째 단계로 볼 수 있으며, 이것은 Scala를 위해 설계된 것으로 이 언어의 모든 사용 가능한 기능을 이용한다.
다행히도 Scala의 언어로서의 특성은 TypeScript의 유형 시스템 단계에서의 특성과 매우 비슷하며, 어떤 경우 TypeScript 유형 시스템은 더욱 유연하다(즉 교차 및 병렬 유형 지원).
이 밖에 ZIO Prelude는 이전에 부차적인 수학 구조로 여겨졌던 더욱 광범위한 범위를 연구했다.Functorfp-ts 을 살펴보겠습니다. 우리는 사물의 소형화를 유지하기 위해 하나의 정의만 열거할 것입니다.
export interface Functor<F> {
  readonly URI: F
  readonly map: <A, B>(fa: HKT<F, A>, f: (a: A) => B) => HKT<F, B>
}
이와 유사하게 다른 fp 언어에서 정의한다. 예를 들어 purescript & haskell 이 유형류는 일종의 편견을 나타낸다. 실제로 범주론에서 편지는 협동하거나 역변할 수 있다. 여기서 우리는 Functor 명칭을 특정한 상황과 연결시킨다
이제 Functor 가 어떻게 분류되어 정의되었는지 살펴보겠습니다.
Functor 사이의 Categoriesobjectsmorphisms의 반사로 범주 구조를 유지하고 적어도 두 가지 유형의 Functors, 하나는 morphisms의 방향을 유지하고 다른 반전 방향을 유지한다.
이것들은 Covariant Functor & Contravariant Functor라고 불린다.fp-ts의 상술한 정의에서 우리는 haskell의 편차가 모든 것을 가리킨다CovariantFunctors를 인식했다.
ZIO Prelude는 서로 다른 이름을 사용하고 매우 정교한 디자인(즉 최소 유형 클래스, 조합하기 쉬운)을 이용하여 개념적으로는 같지만 유형 클래스의 실제 규칙에 더욱 가깝다.

협동적

Functor@effect-ts/core의 등가물을 살펴보자.
export interface Covariant<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly map: <A, B>(
    f: (a: A) => B
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, B>
}
코드: core/src/Prelude/Covariant/index.ts
사용된 이름은 Covariant 입니다.
알려진 데이터 유형의 예를 살펴보겠습니다.
export const Covariant = P.instance<P.Covariant<[EitherURI], V>>({
  map: E.map
})
그 중에서 Covariant Functor 모듈은 E 지시 파라미터Either에 사용되는 협방차V = Prelude.V<"E", "+">에서 오차 채널E과 연합 유형이 혼합된 것을 뒤에서 볼 수 있습니다.

명세서


사랑하는 애인을 봅시다Either:
export type Monad<F extends URIS, C = Auto> = IdentityFlatten<F, C> & Covariant<F, C>

export type IdentityFlatten<F extends URIS, C = Auto> = AssociativeFlatten<F, C> &
  Any<F, C>

export interface Any<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly any: <
    N extends string = HKT.Initial<C, "N">,
    K = HKT.Initial<C, "K">,
    Q = HKT.Initial<C, "Q">,
    W = HKT.Initial<C, "W">,
    X = HKT.Initial<C, "X">,
    I = HKT.Initial<C, "I">,
    S = HKT.Initial<C, "S">,
    R = HKT.Initial<C, "R">,
    E = HKT.Initial<C, "E">
  >() => HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, any>
}

export interface AssociativeFlatten<F extends HKT.URIS, C = HKT.Auto>
  extends HKT.Base<F, C> {
  readonly flatten: <
    N extends string,
    K,
    Q,
    W,
    X,
    I,
    S,
    R,
    E,
    A,
    N2 extends string,
    K2,
    Q2,
    W2,
    X2,
    I2,
    S2,
    R2,
    E2
  >(
    ffa: HKT.Kind<
      F,
      C,
      N2,
      K2,
      Q2,
      W2,
      X2,
      I2,
      S2,
      R2,
      E2,
      HKT.Kind<
        F,
        C,
        HKT.Intro<C, "N", N2, N>,
        HKT.Intro<C, "K", K2, K>,
        HKT.Intro<C, "Q", Q2, Q>,
        HKT.Intro<C, "W", W2, W>,
        HKT.Intro<C, "X", X2, X>,
        HKT.Intro<C, "I", I2, I>,
        HKT.Intro<C, "S", S2, S>,
        HKT.Intro<C, "R", R2, R>,
        HKT.Intro<C, "E", E2, E>,
        A
      >
    >
  ) => HKT.Kind<
    F,
    C,
    HKT.Mix<C, "N", [N2, N]>,
    HKT.Mix<C, "K", [K2, K]>,
    HKT.Mix<C, "Q", [Q2, Q]>,
    HKT.Mix<C, "W", [W2, W]>,
    HKT.Mix<C, "X", [X2, X]>,
    HKT.Mix<C, "I", [I2, I]>,
    HKT.Mix<C, "S", [S2, S]>,
    HKT.Mix<C, "R", [R2, R]>,
    HKT.Mix<C, "E", [E2, E]>,
    A
  >
}
코드: core/src/Prelude/Monad/index.ts
약간의 지루함을 제외하고는 E 최대 10개의 다른 유형의 매개 변수를 지원하며, 이 매개 변수들은 실례 단계에 따라 지정한 방차 주석에 따라 동적 혼합을 할 수 있다.
우리는 Monad가 어떻게 서로 다른, 더욱 구체적인, 합법적인 유형류 사이에서 분리되고 있는지 볼 수 있다.
우리는 @effect-ts/coreMonad 평행 연산이 있는 Monad 편지를 읽었다.
기본적으로 aCovariant가 반드시 준수해야 하는 법률을 묘사하였다.
각종 identityAssociative의 몇 가지 실례를 살펴보고 방차가 어떻게 작동하는지 봅시다.
우리는 우선 어떤 유형의 코드를 어떻게 작성하는지 보여주기 위해 범용 조작을 소개할 것이다. 범용 Monad 함수, 이 함수가 정한 Monad 실례를 살펴보고 일련의 조작을 실행할 것이다. 그 중에서 두 번째 조작은 첫 번째 조작의 결과에 달려 있다.
export function chainF<F extends HKT.URIS, C = HKT.Auto>(
  F: Monad<F, C>
): <N2 extends string, K2, Q2, W2, X2, I2, S2, R2, E2, A, B>(
  f: (a: A) => HKT.Kind<F, C, N2, K2, Q2, W2, X2, I2, S2, R2, E2, B>
) => <N extends string, K, Q, W, X, I, S, R, E>(
  fa: HKT.Kind<
    F,
    C,
    HKT.Intro<C, "N", N2, N>,
    HKT.Intro<C, "K", K2, K>,
    HKT.Intro<C, "Q", Q2, Q>,
    HKT.Intro<C, "W", W2, W>,
    HKT.Intro<C, "X", X2, X>,
    HKT.Intro<C, "I", I2, I>,
    HKT.Intro<C, "S", S2, S>,
    HKT.Intro<C, "R", R2, R>,
    HKT.Intro<C, "E", E2, E>,
    A
  >
) => HKT.Kind<
  F,
  C,
  HKT.Mix<C, "N", [N2, N]>,
  HKT.Mix<C, "K", [K2, K]>,
  HKT.Mix<C, "Q", [Q2, Q]>,
  HKT.Mix<C, "W", [W2, W]>,
  HKT.Mix<C, "X", [X2, X]>,
  HKT.Mix<C, "I", [I2, I]>,
  HKT.Mix<C, "S", [S2, S]>,
  HKT.Mix<C, "R", [R2, R]>,
  HKT.Mix<C, "E", [E2, E]>,
  B
>
export function chainF<F>(F: Monad<HKT.UHKT<F>>) {
  return <A, B>(f: (a: A) => HKT.HKT<F, B>) => flow(F.map(f), F.flatten)
}
코드: core/src/Prelude/DSL/dsl.ts
우리는 몇 가지 다른 실례에서 이 통용 data-types 함수를 사용합시다.
import * as IO from "@effect-ts/core/XPure/XIO";
import * as Either from "@effect-ts/core/Classic/Either";
import * as Effect from "@effect-ts/core/Effect";
import { pipe } from "@effect-ts/core/Function";
import { chainF } from "@effect-ts/core/Prelude/DSL";

const chainIO = chainF(IO.Monad);
const chainEither = chainF(Either.Monad);
const chainEffect = chainF(Effect.Monad);

// IO.XIO<number>
const io = pipe(
  IO.succeed(0),
  chainIO((n) => IO.succeed(n + 1))
);

const checkPositive = (n: number): Either.Either<string, number> =>
  n > 0 ? Either.right(n) : Either.left("error");

// Either.Either<string, number>
const either = (n: number) =>
  pipe(
    n,
    checkPositive,
    chainEither((n) => Either.right(n + 1))
  );

// Effect.Effect<{ s: string; } & { n: number; }, string | number, number>
const effect = pipe(
  Effect.accessM((_: { n: number }) =>
    Effect.ifM(Effect.succeed(_.n > 0))(() => Effect.succeed(_.n))(() =>
      Effect.fail("error")
    )
  ),
  chainEffect((n) =>
    Effect.accessM((_: { s: string }) =>
      Effect.ifM(Effect.succeed(_.s.length > 1))(() =>
        Effect.succeed(n + _.s.length)
      )(() => Effect.fail(0))
    )
  )
);
보시다시피 매개변수 chain, Monad 의 혼합은 다음과 같은 인스턴스로 지정된 방차에 따라 다릅니다.
// for Effect
export type V = P.V<"R", "-"> & P.V<"E", "+">

// for Either
export type V = P.V<"E", "+">

실용적


이 오랜 친구chainF를 살펴보자. 우선 주의해야 할 것은, R 완전히 E 육지와 같지 않다는 것이다!
export type Applicative<F extends URIS, C = Auto> = IdentityBoth<F, C> & Covariant<F, C>

export type IdentityBoth<F extends URIS, C = Auto> = AssociativeBoth<F, C> & Any<F, C>

export interface AssociativeBoth<F extends HKT.URIS, C = HKT.Auto>
  extends HKT.Base<F, C> {
  readonly both: <N2 extends string, K2, Q2, W2, X2, I2, S2, R2, E2, B>(
    fb: HKT.Kind<F, C, N2, K2, Q2, W2, X2, I2, S2, R2, E2, B>
  ) => <N extends string, K, Q, W, X, I, S, R, E, A>(
    fa: HKT.Kind<
      F,
      C,
      HKT.Intro<C, "N", N2, N>,
      HKT.Intro<C, "K", K2, K>,
      HKT.Intro<C, "Q", Q2, Q>,
      HKT.Intro<C, "W", W2, W>,
      HKT.Intro<C, "X", X2, X>,
      HKT.Intro<C, "I", I2, I>,
      HKT.Intro<C, "S", S2, S>,
      HKT.Intro<C, "R", R2, R>,
      HKT.Intro<C, "E", E2, E>,
      A
    >
  ) => HKT.Kind<
    F,
    C,
    HKT.Mix<C, "N", [N2, N]>,
    HKT.Mix<C, "K", [K2, K]>,
    HKT.Mix<C, "Q", [Q2, Q]>,
    HKT.Mix<C, "W", [W2, W]>,
    HKT.Mix<C, "X", [X2, X]>,
    HKT.Mix<C, "I", [I2, I]>,
    HKT.Mix<C, "S", [S2, S]>,
    HKT.Mix<C, "R", [R2, R]>,
    HKT.Mix<C, "E", [E2, E]>,
    readonly [A, B]
  >
}
코드: core/src/Prelude/Applicative/index.ts
이보다 더 쉬운 것은 없다. 왜냐하면 우리는 하나Applicative가 하나Applicative의 편지이고 하나의 항등식과 하나Monad의 연산Haskell을 가지고 있기 때문이다.
이론적으로 말하자면 그것은 Applicative의 고전적인 변체와 같지만 법칙의 각도와 가용성의 측면에서 보면 더욱 뚜렷하다.
그 밖에 만약에 우리가 이론에 따라 읽을 수 있다면 ncatlab.org:

In computer science, applicative functors (also known as idioms) are the programming equivalent of lax monoidal functors with a tensorial strength in category theory.


만약 당신이 관련된 용어를 알고 있다면, 당신은 경전Covariant에 비해 이 정의가 최종적으로 이론에 가깝다는 것을 알게 될 것이다.Associative 편지에 사용할 수 있는 DSL을 살펴보겠습니다.
import * as Either from "@effect-ts/core/Classic/Either";
import * as DSL from "@effect-ts/core/Prelude/DSL";

const struct = DSL.structF(Either.Applicative);
const tupled = DSL.tupledF(Either.Applicative);

// Either.Either<never, { a: number; b: number; c: number; }>
const resultStruct = struct({
  a: Either.right(0),
  b: Either.right(1),
  c: Either.right(2),
});

// Either.Either<never, [number, number, number]>
const resultTupled = tupled(Either.right(0), Either.right(1), Either.right(2));
우리는 그것을 연습으로 삼아 독자들이 이 성명에서 Both & apap 성명을 내보낼 수 있도록 하고, 반대로 (제시: 당신은 Applicative 에서 사용할 수 있는 함수를 사용할 수 있습니다.)

두루 훑어볼 수 있었어


이 사랑하는 옛 친구를 봅시다Monad:
export interface Foreach<F extends HKT.URIS, C = HKT.Auto> {
  <G extends HKT.URIS, GC = HKT.Auto>(G: IdentityBoth<G, GC> & Covariant<G, GC>): <
    GN extends string,
    GK,
    GQ,
    GW,
    GX,
    GI,
    GS,
    GR,
    GE,
    A,
    B
  >(
    f: (a: A) => HKT.Kind<G, GC, GN, GK, GQ, GW, GX, GI, GS, GR, GE, B>
  ) => <FN extends string, FK, FQ, FW, FX, FI, FS, FR, FE>(
    fa: HKT.Kind<F, C, FN, FK, FQ, FW, FX, FI, FS, FR, FE, A>
  ) => HKT.Kind<
    G,
    GC,
    GN,
    GK,
    GQ,
    GW,
    GX,
    GI,
    GS,
    GR,
    GE,
    HKT.Kind<F, C, FN, FK, FQ, FW, FX, FI, FS, FR, FE, B>
  >
}

export interface Traversable<F extends HKT.URIS, C = HKT.Auto>
  extends HKT.Base<F, C>,
    Covariant<F, C> {
  readonly foreachF: Foreach<F, C>
}
코드: core/src/Prelude/Traversable/index.tsApplicative 함수(최초칭 fp-ts의 명칭을 제외하고는 고전 버전과 별다른 차이가 없다.
사용법을 살펴보겠습니다.
import * as Either from "@effect-ts/core/Classic/Either";
import * as Array from "@effect-ts/core/Classic/Array";
import * as Record from "@effect-ts/core/Classic/Record";
import { pipe } from "@effect-ts/core/Function";
import { sequenceF } from "@effect-ts/core/Prelude";

const foreachArray = Array.Traversable.foreachF(Either.Applicative);

// Either.Either<string, Array.Array<number>>
const resultArray = pipe(
  [0, 1, 2, 3],
  foreachArray((n) => (n > 2 ? Either.left("error") : Either.right(n)))
);

const foreachRecord = Record.Traversable.foreachF(Either.Applicative);

// Either.Either<string, Readonly<Record<"a" | "b" | "c" | "d", number>>>
const resultRecord = pipe(
  {
    a: 0,
    b: 0,
    c: 0,
    d: 0,
  },
  foreachRecord((n) => (n > 2 ? Either.left("error") : Either.right(n)))
);

const sequenceArray = sequenceF(Array.Traversable)(Either.Applicative);

// Either.Either<string, Array.Array<number>>
const sequenceArrayResult = sequenceArray([
  Either.left("error"),
  Either.right(0),
  Either.right(1),
  Either.right(2),
]);

신분


친애하는 노인Prelude/DSL:
export interface Identity<A> extends Associative<A> {
  readonly identity: A
}

export interface Associative<A> extends Closure<A> {
  readonly Associative: "Associative"
}

export interface Closure<A> {
  combine(r: A): (l: A) => A
}
앞에서 말한 바와 같이 법칙을 모르는 상황에서 우리는 TraversableforeachF 원소의 traverse 관련 연산을 읽을 수 있다.

접을 수 있었어

Monoid 특별한 점은 없습니다.
export type Foldable<F extends URIS, C = Auto> = ReduceRight<F, C> &
  Reduce<F, C> &
  FoldMap<F, C>

export interface Reduce<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly reduce: <A, B>(
    b: B,
    f: (b: B, a: A) => B
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => B
}

export interface ReduceRight<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly reduceRight: <A, B>(
    b: B,
    f: (a: A, b: B) => B
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => B
}

export interface FoldMap<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly foldMap: FoldMapFn<F, C>
}

export interface FoldMapFn<F extends HKT.URIS, C = HKT.Auto> {
  <M>(I: Identity<M>): <A>(
    f: (a: A) => M
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => M
}
몇 가지 Monoid 실례를 사용해 봅시다.
import * as Array from "@effect-ts/core/Classic/Array";
import * as Record from "@effect-ts/core/Classic/Record";
import * as Identity from "@effect-ts/core/Classic/Identity";

const fromArray = Record.fromFoldable(Identity.string, Array.Foldable);

// Readonly<Record<string, string>>
const record = fromArray([
  ["a", "foo"],
  ["b", "bar"],
]);

모듈 구조

combine 패키지는 다음 디렉토리에 따라 구성됩니다.
  • identity: 어디서나 사용할 수 있는 경량급 모듈 및 일반 유형(브라우저, 노드)
  • Foldable: 효과 기반 모듈로 주로 노드를 대상으로 개발되었습니다. 이 모듈은 다양한 데이터 유형을 가진 고도로 병행되고 테스트 가능한 서비스를 구축하는 데 사용되는 완전한 세트입니다. Foldable이것은 전단 개발에도 사용할 수 있지만 원가 효율을 고려해야 한다. 만약에 프로젝트가 충분하다면 비교적 작은 프로젝트에서 프로젝트의 할당을 바탕으로 하고 @effect-ts/core 같은 @effect-ts/core/Classic 특정한 사용 데이터 유형이 더욱 바람직하기 때문이다.
  • @effect-ts/core/Effect: 함수 기반 유틸리티(예: Fiber, FiberRef, Layer, Managed, Promise, Queue, Ref, RefM, Schedule, Scope, Semaphore, Stream, Supervisor
  • Classic: 새로운 유형 정의 및 일반적인 새로운 유형
  • Async: 패턴 일치 및 교차에 사용되는 유틸리티 그룹
  • @effect-ts/core/Function: pipe의 데이터 유형을 바탕으로 하는 효율적인 동기화 데이터 유형으로 역변 상태 입력, 협동 상태 출력, 역변 읽기기, 협동 오차, 출력을 지원한다.@effect-ts/core/Newtype의 목적은 특정 기능을 충족시킬 수 있는 다양한 데이터 유형을 구축하는 기초이다.그것도 매우 경량급이다. 만약 서로 다른 데이터 유형을 뛰어넘어 사용한다면, 그것은 특히 효과적일 것이다.@effect-ts/core/Utils 또한 @effect-ts/core/XPure 데이터 형식을 지원하는 데 사용되며, 이 데이터 형식은 본 컴퓨터에서 XPure 라는 원어를 포함하고 있습니다.
  • XPure: 내부 사용법
  • 좋은 웹페이지 즐겨찾기