PHPスクリプトのエンコード(もう一度)


私がトランスフォームをサポートすることができて、冗長な宣言の必要性を減らすそのような方法で、タイプスクリプトでより高い親切なタイプ(HKTS)をコード化する新しい方法に熱心に書いていたとき、それはおよそ1年前でした.
記事は次のとおりです.https://www.matechs.com/blog/encoding-hkts-in-ts4-1
年にたくさん起こったEffect 今年、多くの新しい貢献者と多くの新しい生態系プロジェクトと私はより興奮することができませんでした.
すべての利益の背後には多くの努力があり、時には複雑にビットごとに取り組んで複数の段階に繰り返す必要があります簡単に見えるものに到達する.
これは、我々が集団的に何か単純な何年もの間逃した方法の物語です.

上記の記事を読んでみたいのなら、ここからゼロから始めます.

なぜ我々はHKTSが必要ですか?
モジュール間で共通の機能を実装したいなら、例えば以下の関数を取りましょう.
declare const mapArray: 
  <A>(self: Array<A>, f: (a: A) => B) => Array<B>

declare const mapTree: 
  <A>(self: Tree<A>, f: (a: A) => B) => Tree<B>

declare const mapOption: 
  <A>(self: Option<A>, f: (a: A) => B) => Option<B>
私たちは、上記の署名のどれだけが一般的であるかについて気付くことができます、実際、彼らは、彼らが目標とするタイプを除いて、すべて等しいですArray|Tree|Option .
この動作を記述するためのインターフェイスを定義したいと思います.残念なことに、それはどのように明白ではありません.
つ目の夢を見ましょう
interface Mappable<F<~>> {
  readonly map: 
    <A, B>(self: F<A>, f: (a: A) => B) => F<B>
}
こうすると、
declare const mapArray: Mappable<Array>["map"]
declare const mapTree: Mappable<Tree>["map"]
declare const mapOption: Mappable<Option>["map"]
実際には以下のように定義できます:
declare const ArrayMappable: Mappable<Array>
declare const TreeMappable: Mappable<Tree>
declare const OptionMappable: Mappable<Option>
また、次のような汎用関数を実装することもできます.
const stringify = 
  <F>(T: Mappable<F>) => 
  (self: F<number>): F<string> => 
  T.map(self, (n) => `number: ${n}`)
以下のようにします.
const stringifiedArray: Array<string> = 
  stringify(MappableArray)([0, 1, 2])
用語を紹介するF<~>Higher Kinded Type and interface Mappable<F<~>>TypeClass .
これまでのところ、単一のパラメータを持つデータ型のみが見られましたOption<~> or Array<~> , また、複数のパラメータを持つデータ型もありますEither<~, ~> or Effect<~, ~, ~> そして、私たちは同じセットアップでそれらを含むことを望みます.
interface Mappable<F<~, ~, ~>> {
  readonly map: <R, E, A, B>(
    self: F<R, E, A>, 
    f: (a: A) => B
  ) => F<R, E, B>
}
しかし、我々は現在、誰のものを定義する問題がありますA インEither 最初または2番目のパラメータ?我々は、どこでいくつかの規則を持つことができましたE 常に2番目と2番目ですR 常に最初でありA 常に最後のしかしそれはあまりにも柔軟性がありません.
実際にはこれに一般的な解決策はありませんが、多くのアイデアが存在します.例えばScalaでは、型ラムダ(型パラメータ間のマッパのソート)を定義することができます.
夢を止めましょうF<~> 有効なタイプスクリプトでさえありません、うまくいけば、我々は我々が持ちたいものの考えを得ました.

お話ししましょう
最近、私は、より良いデザインは、無関係なものについてあなたに話す必要を最小にするデザインであるとますます聞いていました、前のencodingsで、このリストは巨大です、しかし、私は現在、私が一つの「トリック」(信頼できる予想されたふるまいと確認される)についてあなたに話さなければならないだけであると言わなければなりません.
The this タイプ単一化論理
interface MyInterface {
  readonly x?: unknown
}

type X = (MyInterface & { readonly x: number })["x"]
あなたは、タイプを正当に予想しますX あるnumber あれunknown & number = number .
インターフェイスの中で参照することもできますthis タイプも期待できます.
interface MyInterface {
  readonly x?: unknown
  readonly y: this["x"]
}

type Y = (MyInterface & { readonly x: number })["y"]
それは驚くべきことだy 常にunknown 代わりにthis パラメータは常に拡張子でもカレント型を表すので特別ですX extends Y extends Z , と定義されるものthis インZX . これは、クラスやインタフェースのプレーンOOP継承のための使い方について考えるなら、かなり論理的です.

単一パラメータ符号化
上記の線に沿って何かを定義しましょう.
interface HKT {
  // will reference the A type
  readonly _A?: unknown

  // will represent the computed type
  readonly type?: unknown
}
今では一般的なタイプを参照する方法が必要ですKind<F, A> 意味としてF<A> , それはちょっとトリッキーです.
type Kind<F extends HKT, A> = 
  F extends {
    readonly type: unknown
  } ?
  // F has a type specified, it is concrete (like F = ArrayHKT)
  (F & {
    readonly _A: A
  })["type"] :
  // F is generic, we need to mention all of the type parameters
  // to guarantee that they are never excluded from type checking
  {
    readonly _F: F
    readonly _A: () => A
  }
それから、
interface ArrayHKT extends HKT {
  readonly type: Array<this["_A"]>
}

type X = Kind<ArrayHKT, number>
そして、X is number[] .
今我々は定義することができますMappable 以前から
interface Mappable<F extends HKT> {
  readonly map: 
    <A, B>(self: Kind<F, A>, f: (a: A) => B) => Kind<F, B>
}
定義できます:
const MappableArray: Mappable<ArrayHKT>
我々の署名をチェックすることができますMappableArray["map"] そして、<A, B>(self: A[], f: (a: A) => B) => B[] .
上記のように書くこともできます.
const stringify = 
  <F extends HKT>(T: Mappable<F>) => 
  (self: Kind<F, number>): Kind<F, string> => 
  T.map(self, (n) => `number: ${n}`)
そして、仕事はされます.

マルチパラメータ符号化
ここで本当に知っている唯一のことはKind すべての型パラメータをそれぞれの分散で記述する必要があります.例えば、1つの入力とR、E、Aという2つの出力をサポートしたいとしましょう.
interface HKT {
  readonly _R?: unknown
  readonly _E?: unknown
  readonly _A?: unknown

  readonly type?: unknown
}

type Kind<F extends HKT, R, E, A> = 
  F extends {
    readonly type: unknown
  } ?
  (F & {
    readonly _R: R
    readonly _E: E
    readonly _A: A
  })["type"] :
  {
    readonly _F: F
    readonly _R: (_: R) => void
    readonly _E: () => E
    readonly _A: () => A
  }

interface Mappable<F extends HKT> {
  readonly map: 
    <R, E, A, B>(
      self: Kind<F, R, E, A>,
      f: (a: A) => B
    ) => Kind<F, R, E, B>
}

interface ArrayHKT extends HKT {
  readonly type: Array<this["_A"]>
}

const stringify = 
  <F extends HKT>(T: Mappable<F>) => 
  <R, E>(self: Kind<F, R, E, number>) => 
  T.map(self, (n) => `number: ${n}`)

declare const ArrayMappable: Mappable<ArrayHKT>

const res = stringify(ArrayMappable)([0, 1, 2])

typeclassによる推論
完全に符号化Higher Kinded Types そして、私たちはTypeClass ライクMappable , 推論を改善するためにはF パラメータが設定されていない場合には、以下のようなオプションのパラメータを追加することによって行うことができます.
interface TypeClass<F extends HKT> {
  readonly _F?: F
}

interface Mappable<F extends HKT> extends TypeClass<F> {
  readonly map: 
    <R, E, A, B>(
      self: Kind<F, R, E, A>,
      f: (a: A) => B
    ) => Kind<F, R, E, B>
}

終わり
この符号化の完全なプロトタイプを見つけることができますhttps://github.com/mikearnaldi/hkt-new 変圧器の使用を含むValidationT , ReaderT , など).
このポスト効果を書く瞬間に、まだこれに移動する古い符号化と進行を使用しますhttps://github.com/Effect-TS/core/issues/1005 .