効果のコア:ZIIOプレリュードインスパイア&モジュール構造


第1の記事では、我々は、2009年に使用されるHKTSのユニークな符号化の背後にある原則を説明しました@effect-ts/core , 詳細を見る時間です.
我々は、探索することから始めますType-Classes 利用できると我々は徐々にいくつかの使用例を作成します.
最後に、モジュール構造と利用可能なA - LA - Carteについて説明します.

プロジェクト設定


単純な新しいプロジェクトから始めましょう(Scala/Haskellから来た場合に限ってみてください.
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.

ZIOプレリュードタイプクラス入門


まず最初に、古典的なタイプ階層を改訂する理論と理由の少しで始めましょう.
静的に型付けされた関数型プログラミングは、今日我々が効率的にHaskellとその設計原理にルーツを持っていることを知っている何年もの間、我々はコミュニティとして、一つずつ借り入れの原則を行使し、別の言語にその道を見つけることを行った.
つの言語から別の言語への機能を移植するプロセスは簡単ではありません、そして、それは複数のステップを必要とします.
Haskellのタイプシステムはカテゴリー理論に触発されます、しかし、数学的にそれはHM家族の言語で意味をなす理論の特定の部分集合に集中する「近似」だけです.Haskellで作られた同じ仮定が我々の中で保持されていないかもしれないので(例えば、すべての機能がカレーディングされているように)、他の理論に対して特に無視するべきではありません.
ZIOのプレリュードは、抽象化の2番目のステップとScalaに機能プログラミングの概念の適応、それはScalaとレバレッジのために設計されているすべての機能は、言語で使用可能です.
我々にとって幸運なことは、言語としてScalaの特徴はタイプ・システム・レベルでのタイプスクリプトの特徴に非常に似ています、そして、いくつかのケースでは、typescript型システムはさらに柔軟です(すなわち、サポート交差点と組合タイプ).
さらに、ジーニョ・プレリュードは、以前は二次として認識された数学からのより広い範囲の構造を見ます.
見てみましょうFunctor からfp-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 このtypeclassはバイアスを示します、カテゴリー理論において、ファンターが共変的であるか反抗的でありえるとき、我々はここで我々を結びつけますFunctor 特定のケースの名前
ではどうやって見ましょうかFunctor 定義は

エーFunctor の間Categories のマッピングobjects and morphisms これは、分類構造を保持し、少なくとも2種類のFunctors , その方向を保つものmorphisms そして、方向を逆にするもの.
これらはCovariant Functor & Contravariant Functor .
上記の定義からfp-ts 私たちはhaskell 偏見、すべてが向いているCovariant Functors .
ZIOプレリュードは異なった命名を使用して、非常に直交したデザイン(すなわち、最小限の型クラス(簡単に構成可能な))を活用します.

共変


の等価物を見てみましょう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 と同じCovariant Functor .
既知のデータ型のいくつかの例を見てみましょう.
export const Covariant = P.instance<P.Covariant<[EitherURI], V>>({
  map: E.map
})
どこEEither モジュールV = Prelude.V<"E", "+"> パラメータの共分散を示すEEither エラーチャネルE 私たちが後で表示されるようにユニオンのタイプとミックスします.

モンド


親愛なる愛を見ましょうMonad :
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
わずかに冗長であることは別として、@effect-ts/core 動的にインスタンスレベルで指定された分散アノテーションに基づいてミックスすることができます10種類のタイプのパラメータをサポートします.
どのようによく見ることができますMonad 異なった、より具体的な、法定タイプクラスの向こう側に直交して分離されます.
読みましたMonadCovariant ファンクションidentityAssociative 操作を平らにする.
それ自体は法律を説明するMonad 尊重しなければなりません.
のいくつかの例を見てみましょうMonad 様々なdata-types そして、分散の仕組みを見てみましょう.
まず、どのような種類で動作するコードをどのように書くかを紹介する一般的な操作を紹介しますchain インスタンスを指定した関数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
この汎用を使いましょうchainF いくつかの異なるインスタンスの関数:
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))
    )
  )
);
パラメータを見ることができますR , E は以下のように指定されたインスタンスの分散によって異なる.
// for Effect
export type V = P.V<"R", "-"> & P.V<"E", "+">

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

応募


古き良き友を見ましょうApplicative , 最初に注意すべきことはApplicative から完全に独立しているMonad 本当に好きではないHaskell 陸!
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
何も簡単に、我々は読んでApplicativeCovariant アイデンティティーとAAssociative 操作Both .
これは理論的には古典的なバリアントと同じですap しかし、法律上の観点や使いやすさの観点からはるかに明確です.
また、私たちが理論によって行くならば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.


あなたが関係している用語を知っているならば、あなたは最後にこの定義が古典に比べて理論に非常に近いと認識するでしょうap .
いくつかのDSLを見てみましょうApplicative 関数
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));
我々は、読者のための運動としてそれを引き出すために残すMonad & Applicative の宣言fp-ts ヒントとしては、次の関数を使用できますPrelude/DSL ).

横断できる


親愛なる旧友を見てみましょうTraversable :
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.ts
何も例外的に古典的なバージョンとは別にの名前からforeachF 関数名traverse ).
その使い方を見てみましょう.
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),
]);

アイデンティティ


親愛なる老人Monoid :
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
}
前に、我々はそれを読むことができる法律を知らない前にMonoidcombine による連想操作identity 要素.

折り畳み可能な


何も特別なFoldable :
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
}
を使ってみましょうFoldable インスタンス.
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"],
]);

モジュール構造


The @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 : NewTypeの定義と共通の型
  • @effect-ts/core/Utils : パターンマッチングと交差のためのユーティリティの小セット
  • @effect-ts/core/XPure : データ型XPure , 逆の状態入力、共変状態出力、反対のリーダー、共変エラー、出力をサポートする効率的な同期データ型.…の目的XPure 特定の機能を満たすことができる複数のデータ型を構築するための基礎となる.また、非常に軽量であり、特に別のデータ型間で使用する場合、効率的にすることができます.XPure また、Classic/Sync 原始的に存在するデータ型Effect .
  • @effect-ts/core/Modules : 内部使用