TS4에서 HKT 인코딩1
fpts에서 모든 유형은 일련의 유형 레벨 매핑에 기록됩니다. 이러한 매핑 색인은
URI -> Concrete Type
이고 매핑 유형마다 다릅니다.export interface URItoKind<A> {}
export interface URItoKind2<E, A> {}
export interface URItoKind3<R, E, A> {}
export interface URItoKind4<S, R, E, A> {}
이러한 유형의 레벨의 기록은 모듈 확장 기능을 사용하여 점차적으로 채워진다.이 기록에 어떻게 연결되는지 살펴보겠습니다 Either & Option
.export const URI = "Either"
export type URI = typeof URI
declare module "./HKT" {
interface URItoKind2<E, A> {
readonly [URI]: Either<E, A>
}
}
export const URI = "Option"
export type URI = typeof URI
declare module "./HKT" {
interface URItoKind<A> {
readonly [URI]: Option<A>
}
}
향상되면 기록은 다음과 같습니다.export interface URItoKind<A> {
Option: Option<A>
...
}
export interface URItoKind2<E, A> {
Either: Either<E, A>
...
}
export interface URItoKind3<R, E, A> {
ReaderTaskEither: ReaderTaskEither<R, E, A>
...
}
export interface URItoKind4<S, R, E, A> {
StateReaderTaskEither: StateReaderTaskEither<S, R, E, A>
...
}
이러한 유형에 액세스하려면 올바른 레코드 키를 획득하고 매개 변수를 입력해야 합니다.type Result = URItoKind<string, number>["Either"]
대응 대상:Either<string, number>
이 방법을 사용하여 fp ts를 정의합니다.export type URIS = keyof URItoKind<any>
export type URIS2 = keyof URItoKind2<any, any>
export type URIS3 = keyof URItoKind3<any, any, any>
export type URIS4 = keyof URItoKind4<any, any, any, any>
export type Kind<URI extends URIS, A> = URI extends URIS ? URItoKind<A>[URI] : any
export type Kind2<URI extends URIS2, E, A> = URI extends URIS2
? URItoKind2<E, A>[URI]
: any
export type Kind3<URI extends URIS3, R, E, A> = URI extends URIS3
? URItoKind3<R, E, A>[URI]
: any
export type Kind4<URI extends URIS4, S, R, E, A> = URI extends URIS4
? URItoKind4<S, R, E, A>[URI]
: any
이제 액세스할 수 있습니다.type Result = Kind2<"Either", string, number>
이러한 구조를 통해 URI가 지정되지 않은 일반 인터페이스를 작성할 수 있습니다.예를 들어 다음과 같이 쓸 수 있습니다.
export interface Functor1<F extends URIS> {
readonly URI: F
readonly map: <A, B>(fa: Kind<F, A>, f: (a: A) => B) => Kind<F, B>
}
또한const functorOption: Functor1<"Option"> = {
URI: "Option",
map: ... // map is now correctly typed to work with Option<*>
}
분명히 이것은 다른 종류를 개괄하기에는 부족하다.fp ts에서는 각 유형의 typeclass (이 문제에서 인터페이스는 공통 URI를 가진다) 에 대해 여러 가지 정의가 있음을 발견할 수 있습니다.export interface Functor1<F extends URIS> {
readonly URI: F
readonly map: <A, B>(fa: Kind<F, A>, f: (a: A) => B) => Kind<F, B>
}
export interface Functor2<F extends URIS2> {
readonly URI: F
readonly map: <E, A, B>(fa: Kind2<F, E, A>, f: (a: A) => B) => Kind2<F, E, B>
}
export interface Functor2C<F extends URIS2, E> {
readonly URI: F
readonly _E: E
readonly map: <A, B>(fa: Kind2<F, E, A>, f: (a: A) => B) => Kind2<F, E, B>
}
export interface Functor3<F extends URIS3> {
readonly URI: F
readonly map: <R, E, A, B>(fa: Kind3<F, R, E, A>, f: (a: A) => B) => Kind3<F, R, E, B>
}
export interface Functor3C<F extends URIS3, E> {
readonly URI: F
readonly _E: E
readonly map: <R, A, B>(fa: Kind3<F, R, E, A>, f: (a: A) => B) => Kind3<F, R, E, B>
}
export interface Functor4<F extends URIS4> {
readonly URI: F
readonly map: <S, R, E, A, B>(
fa: Kind4<F, S, R, E, A>,
f: (a: A) => B
) => Kind4<F, S, R, E, B>
}
보시다시피 이 네 가지 유형을 제외하고는 *C
인터페이스가 있습니다. E
파라미터에 제약을 추가하는 데 사용됩니다.이것은 Validation
에서 사용되는데 그 중에서 E
는 Error
채널을 표시하고 우리는 Monoid<E>
에 최종적으로 오류를 조합할 것을 요구한다.이제 이런 유형을 어떻게 사용하는지 봅시다.우리는 어떻게 범형
Functor
을 사용하는 함수를 작성합니까?기본적인 상황부터 우리는 일반 함수
addOne
가 필요하다. 이것은 추가를 통해 디지털 출력을 비추는 것이다.function addOne<URI extends URIS>(F: Functor1<URI>) {
return (fa: Kind<F, number>): Kind<F, number> => F.map(fa, (n) => n + 1)
}
적당한 typeclass 실례로 이 함수를 호출하면 데이터 형식에 특정한 함수를 생성합니다.const addOneOption = addOne(functorOption) // (fa: Option<number>) => Option<number>
우리는 리로드를 통해 다양한 유형을 홍보하고 지원할 수 있다.function addOne<URI extends URIS4, E>(
F: Functor4C<URI, E>
): <S, R>(fa: Kind4<F, S, R, E, number>) => Kind4<F, S, R, E, number>
function addOne<URI extends URIS4>(
F: Functor4<URI>
): <S, R, E>(fa: Kind4<F, S, R, E, number>) => Kind4<F, S, R, E, number>
function addOne<URI extends URIS3, E>(
F: Functor3C<URI, E>
): <R>(fa: Kind3<F, R, E, number>) => Kind3<F, R, E, number>
function addOne<URI extends URIS3>(
F: Functor3<URI>
): <R, E>(fa: Kind3<F, R, E, number>) => Kind3<F, R, E, number>
function addOne<URI extends URIS2, E>(
F: Functor2C<URI, E>
): (fa: Kind2<F, E, number>) => Kind2<F, E, number>
function addOne<URI extends URIS2>(
F: Functor2<URI>
): <E>(fa: Kind2<F, E, number>) => Kind<F, E, number>
function addOne<URI extends URIS>(
F: Functor1<URI>
): (fa: Kind<F, number>) => Kind<F, number>
function addOne(F: any) {
return (fa: any): any => F.map(fa, (n) => n + 1)
}
유일한 번거로움은 매우 무서운 기본 상황 (어떤 것, 어떤 것, 어떤 것) 을 정의하는 것이다.fp ts에서 다음과 같이 정의할 수 있습니다.
export interface HKT<URI, A> {
readonly _URI: URI
readonly _A: A
}
export interface HKT2<URI, E, A> extends HKT<URI, A> {
readonly _E: E
}
export interface HKT3<URI, R, E, A> extends HKT2<URI, E, A> {
readonly _R: R
}
export interface HKT4<URI, S, R, E, A> extends HKT3<URI, R, E, A> {
readonly _S: S
}
현재 우리는 Functor
에 특정한 HKT
인터페이스를 정의할 수 있다. 예를 들어 다음과 같다.export interface Functor<F> {
readonly URI: F
readonly map: <A, B>(fa: HKT<F, A>, f: (a: A) => B) => HKT<F, B>
}
이 옵션을 사용하여 기본값을 입력합니다.function addOne<URI extends URIS4, E>(
F: Functor4C<URI, E>
): <S, R>(fa: Kind4<F, S, R, E, number>) => Kind4<F, S, R, E, number>
function addOne<URI extends URIS4>(
F: Functor4<URI>
): <S, R, E>(fa: Kind4<F, S, R, E, number>) => Kind4<F, S, R, E, number>
function addOne<URI extends URIS3, E>(
F: Functor3C<URI, E>
): <R>(fa: Kind3<F, R, E, number>) => Kind3<F, R, E, number>
function addOne<URI extends URIS3>(
F: Functor3<URI>
): <R, E>(fa: Kind3<F, R, E, number>) => Kind3<F, R, E, number>
function addOne<URI extends URIS2, E>(
F: Functor2C<URI, E>
): (fa: Kind2<F, E, number>) => Kind2<F, E, number>
function addOne<URI extends URIS2>(
F: Functor2<URI>
): <E>(fa: Kind2<F, E, number>) => Kind<F, E, number>
function addOne<URI extends URIS>(
F: Functor1<URI>
): (fa: Kind<F, number>) => Kind<F, number>
function addOne<URI>(F: Functor<URI>) {
return (fa: HKT<URI, number>): HKT<URI, number> => F.map(fa, (n) => n + 1)
}
짧고 실용적이지 않습니까?우리는 functors
의 작문을 한 편 쓰자.export interface FunctorComposition<F, G> {
readonly map: <A, B>(fa: HKT<F, HKT<G, A>>, f: (a: A) => B) => HKT<F, HKT<G, B>>
}
export interface FunctorCompositionHKT1<F, G extends URIS> {
readonly map: <A, B>(fa: HKT<F, Kind<G, A>>, f: (a: A) => B) => HKT<F, Kind<G, B>>
}
export interface FunctorCompositionHKT2<F, G extends URIS2> {
readonly map: <E, A, B>(fa: HKT<F, Kind2<G, E, A>>, f: (a: A) => B) => HKT<F, Kind2<G, E, B>>
}
export interface FunctorCompositionHKT2C<F, G extends URIS2, E> {
readonly map: <A, B>(fa: HKT<F, Kind2<G, E, A>>, f: (a: A) => B) => HKT<F, Kind2<G, E, B>>
}
export interface FunctorComposition11<F extends URIS, G extends URIS> {
readonly map: <A, B>(fa: Kind<F, Kind<G, A>>, f: (a: A) => B) => Kind<F, Kind<G, B>>
}
export interface FunctorComposition12<F extends URIS, G extends URIS2> {
readonly map: <E, A, B>(fa: Kind<F, Kind2<G, E, A>>, f: (a: A) => B) => Kind<F, Kind2<G, E, B>>
}
export interface FunctorComposition12C<F extends URIS, G extends URIS2, E> {
readonly map: <A, B>(fa: Kind<F, Kind2<G, E, A>>, f: (a: A) => B) => Kind<F, Kind2<G, E, B>>
}
export interface FunctorComposition21<F extends URIS2, G extends URIS> {
readonly map: <E, A, B>(fa: Kind2<F, E, Kind<G, A>>, f: (a: A) => B) => Kind2<F, E, Kind<G, B>>
}
export interface FunctorComposition2C1<F extends URIS2, G extends URIS, E> {
readonly map: <A, B>(fa: Kind2<F, E, Kind<G, A>>, f: (a: A) => B) => Kind2<F, E, Kind<G, B>>
}
export interface FunctorComposition22<F extends URIS2, G extends URIS2> {
readonly map: <FE, GE, A, B>(fa: Kind2<F, FE, Kind2<G, GE, A>>, f: (a: A) => B) => Kind2<F, FE, Kind2<G, GE, B>>
}
export interface FunctorComposition22C<F extends URIS2, G extends URIS2, E> {
readonly map: <FE, A, B>(fa: Kind2<F, FE, Kind2<G, E, A>>, f: (a: A) => B) => Kind2<F, FE, Kind2<G, E, B>>
}
export interface FunctorComposition23<F extends URIS2, G extends URIS3> {
readonly map: <FE, R, E, A, B>(fa: Kind2<F, FE, Kind3<G, R, E, A>>, f: (a: A) => B) => Kind2<F, FE, Kind3<G, R, E, B>>
}
export interface FunctorComposition23C<F extends URIS2, G extends URIS3, E> {
readonly map: <FE, R, A, B>(fa: Kind2<F, FE, Kind3<G, R, E, A>>, f: (a: A) => B) => Kind2<F, FE, Kind3<G, R, E, B>>
}
이거 아직 완전하지 않아...또 다른 제한은 종류마다 독립된 인덱스가 필요하다는 것이다.이것은 typeclass transformers를 매우 비현실적으로 만들었다.
우리의 목표는 더 적은 템플릿 파일로 같은 특성을 얻는 것이다.
이제
@effect-ts/core
에서 사용한 인코딩의 제한 버전을 보여 드리겠습니다.@effect-ts/core
는 10개의 유형 매개 변수(그중 2개는 인코딩 유니버설 키, 즉 기록된 유니버설 키, 비치는 유니버설 키, 수조의 정수 키 등)를 허용하지만, 간단하게 보기 위해 4개(fp ts의 숫자와 같다)로 제한한다.전체 코드는 다음 웹 사이트에서 사용할 수 있습니다.
https://github.com/Matechs-Garage/matechs-effect/tree/master/packages/core/src/Prelude/HKT
그리고 TypeClass(영감은 zio prelude):
https://github.com/Matechs-Garage/matechs-effect/tree/master/packages/core/src/Prelude
첫 번째 생각은 여러 개의 기록이 아니라 유형 레벨의 기록 수를 줄이는 것이다. 우리는 단지 하나의 기록만 가지고 있을 뿐이다.
export interface URItoKind<S, R, E, A> {
XPure: XPure<S, S, R, E, A>
Effect: Effect<R, E, A>
Either: Either<E, A>
Option: Option<A>
}
export type URIS = keyof URItoKind<any, any, any, any>
그리고 우리는 잠시 Kind
를 다음과 같이 정의할 수 있다.export type Kind<F extends URIS, S, R, E, A> = URItoKind<S, R, E, A>[F]
이것은 이미 상당히 많은 템플릿 파일을 삭제했다.그런 다음 TypeClass를 다음과 같이 정의할 수 있습니다.export interface Functor<F extends URIS> {
URI: F
map: <A, A2>(
f: (a: A) => A2
) => <S, R, E>(fa: Kind<F, S, R, E, A>) => Kind<F, S, R, E, A2>
}
및 인스턴스:export const functorOption: Functor<"Option"> = {
URI: "Option",
map: (f) => (fa) => (fa._tag === "None" ? fa : { _tag: "Some", value: f(fa.value) })
}
및 Bifunctor
export interface Bifunctor<F extends URIS> {
URI: F
bimap: <A, A2, E, E2>(
f: (a: A) => A2,
g: (a: E) => E2
) => <S, R>(fa: Kind<F, S, R, E, A>) => Kind<F, S, R, E2, A2>
}
및 인스턴스:export const bifunctorEither: Bifunctor<"Either"> = {
URI: "Either",
bimap: (f, g) => (fa) =>
fa._tag === "Left"
? { _tag: "Left", left: g(fa.left) }
: { _tag: "Right", right: f(fa.right) }
}
보기에는 더 좋지만, 우리는 어떻게 제약을 인코딩합니까? 예를 들어 'E' 를 특정 값으로 고정시킵니까?정답은 구속을 저장하기 위한 일반
C
을 추가하는 것입니다.export type Param = "S" | "R" | "E"
export interface Fix<P extends Param, K> {
Fix: {
[p in P]: K
}
}
export type OrFix<P extends Param, C, V> = C extends Fix<P, infer K> ? K : V
export type Kind<F extends URIS, C, S, R, E, A> = URItoKind<
OrFix<"S", C, S>,
OrFix<"R", C, R>,
OrFix<"E", C, E>,
A
>[F]
TypeClass를 다음으로 변경합니다.export interface Functor<F extends URIS, C = {}> {
URI: F
map: <A, A2>(
f: (a: A) => A2
) => <S, R, E>(fa: Kind<F, C, S, R, E, A>) => Kind<F, C, S, R, E, A2>
}
export interface Bifunctor<F extends URIS, C = {}> {
URI: F
bimap: <A, A2, E, E2>(
f: (a: A) => A2,
g: (a: OrFix<"E", C, E>) => OrFix<"E", C, E2>
) => <S, R>(fa: Kind<F, C, S, R, E, A>) => Kind<F, C, S, R, E2, A2>
}
인스턴스의 코드는 변경되지 않았지만 이제 다음과 같은 방법으로 구속된 인스턴스를 생성할 수 있습니다.export const bifunctorStringValidation: Bifunctor<"Either", Fix<"E", string>> = {
URI: "Either",
bimap: (f, g) => (fa) =>
fa._tag === "Left"
? { _tag: "Left", left: g(fa.left) }
: { _tag: "Right", right: f(fa.right) }
}
// <A, A2, E, E2>(f: (a: A) => A2, g: (a: string) => string) => <S, R>(fa: Either<string, A>) => Either<string, A2>
export const bimapValidation = bifunctorStringValidation.bimap
아직 사용하지 않은 인자가 몇 개 있지만, 원하는 서명을 받았습니다.불행하게도, 여러 개의 등록표와 더 많은 템플릿 파일이 없으면 이러한 환영 유형을 삭제할 수 없지만, 결국, 누가 신경을 쓰겠는가?
하나의 단일한 정의에서 우리는 현재 여러 가지 유형과 여러 가지 잠재적인 제약을 압축할 수 있다. 사실
Fix
은 교차로에 안전하기 때문에 우리는 작성할 수 있다Fix<"E", string> & Fix<"S", number>
.Dell
addOne
의 기능은 다음과 같이 향상되었습니다.function addOne<URI extends URIS, C>(
F: Functor<URI, C>
): <S, R, E>(fa: Kind<URI, C, S, R, E, number>) => Kind<URI, C, S, R, E, number> {
return F.map((n) => n + 1)
}
우리는 그것을 여기에 남겨 둘 수 있고, 이미 좋은 저축이 하나 있지만, 단점도 하나 있다.통용 실현 중의 오류는 왕왕 읽을 수 없게 변한다.왜냐하면 URI는 매우 큰 연합체로서 어떤 종류의 오류든 기본적으로 여러 가지 가능한 조합을 시도하여 사용할 수 없는 오류 메시지를 생성하기 때문이다.우리는 fpts에서 하나의
HKT
를 정의하여 기본적인 실현을 작성하고 나머지는 재부팅에 남겨 두지만 HKT
에 단독 유형 클래스를 정의하고 싶지 않기 때문에 HKT
를 등록표에 추가할 것이다.export interface F_<A> {
_tag: "F_"
A: A
}
export interface URItoKind<S, R, E, A> {
F_: F_<A>
XPure: XPure<S, S, R, E, A>
Effect: Effect<R, E, A>
Either: Either<E, A>
Option: Option<A>
}
이제 F 2;를 사용하여 기본을 정의할 수 있습니다.function addOne<URI extends URIS, C>(
F: Functor<URI, C>
): <S, R, E>(fa: Kind<URI, C, S, R, E, number>) => Kind<URI, C, S, R, E, number>
function addOne(F: Functor<"F_">): (fa: F_<number>) => F_<number> {
return F.map((n) => n + 1)
}
이제 구현의 모든 오류 유형이 F 3;에 지정됩니다.그러나 이러한 "F 3;"솔루션에는 문제가 있습니다.
HKT가 하나면 되는데 우리가 여러 개가 있다면?
예:
export function getFunctorComposition<F, G>(F: Functor<F>, G: Functor<G>): FunctorComposition<F, G> {
return {
map: (fa, f) => F.map(fa, (ga) => G.map(ga, f))
}
}
이를 위해 레지스트리에 "태그"필드를 사용하여 여러 가지 "가짜"유형을 추가합니다.export interface F_<A> {
_tag: "F_"
A: A
}
export interface G_<A> {
_tag: "G_"
A: A
}
export interface H_<A> {
_tag: "H_"
A: A
}
export interface URItoKind<S, R, E, A> {
F_: F_<A>
G_: G_<A>
H_: H_<A>
XPure: XPure<S, S, R, E, A>
Effect: Effect<R, E, A>
Either: Either<E, A>
Option: Option<A>
}
이런 방식을 통해 우리는 여러 개의 HKT를 안전하게'명명'하여 혼합할 수 없도록 하고 더 많은 작업을 할 수 있다. 우리는 이러한 논리를 확장하여 다른 형식의 비원시 URI를 받아들여 사용자 정의 파라미터를 삽입하고 HKT를 구분할 수 있다functor-composition-in-core.충분히 좋아요?아직 접근하지 않았으니 우리는 트랜스포머를 시도해야 한다.
만약 우리가
getFunctorComposition
의Functor
를 원한다면?우리는 어떻게 색인을 다시 작성하지 않은 상황에서 이 점을 할 수 있습니까?Either<E, Option<A>>
의 URI를 조합하기 위해 우리는 변수 모듈을 사용하여 Kind
의 목록을 표시하고 URIS
유형을 귀속적으로 만들 수 있다.export type KindURI = [URIS, ...URIS[]]
export type Kind<F extends KindURI, C, S, R, E, A> = ((...args: F) => any) extends (
head: infer H,
...rest: infer Rest
) => any
? H extends URIS
? Rest extends KindURI
? URItoKind<
OrFix<"S", C, S>,
OrFix<"R", C, R>,
OrFix<"E", C, E>,
Kind<Rest, C, S, R, E, A>
>[H]
: URItoKind<OrFix<"S", C, S>, OrFix<"R", C, R>, OrFix<"E", C, E>, A>[H]
: never
: never
우리의 유형 클래스와 실례는 상응하는 변화가 발생할 것이다.export interface Functor<F extends KindURI, C = {}> {
URI: F
map: <A, A2>(
f: (a: A) => A2
) => <S, R, E>(fa: Kind<F, C, S, R, E, A>) => Kind<F, C, S, R, E, A2>
}
export interface Bifunctor<F extends KindURI, C = {}> {
URI: F
bimap: <A, A2, E, E2>(
f: (a: A) => A2,
g: (a: OrFix<"E", C, E>) => OrFix<"E", C, E2>
) => <S, R>(fa: Kind<F, C, S, R, E, A>) => Kind<F, C, S, R, E2, A2>
}
export const functorOption: Functor<["Option"]> = {
URI: ["Option"],
map: (f) => (fa) => (fa._tag === "None" ? fa : { _tag: "Some", value: f(fa.value) })
}
export const bifunctorEither: Bifunctor<["Either"]> = {
URI: ["Either"],
bimap: (f, g) => (fa) =>
fa._tag === "Left"
? { _tag: "Left", left: g(fa.left) }
: { _tag: "Right", right: f(fa.right) }
}
export const bifunctorStringValidation: Bifunctor<["Either"], Fix<"E", string>> = {
URI: ["Either"],
bimap: (f, g) => (fa) =>
fa._tag === "Left"
? { _tag: "Left", left: g(fa.left) }
: { _tag: "Right", right: f(fa.right) }
}
function addOne<URI extends KindURI, C>(
F: Functor<URI, C>
): <S, R, E>(fa: Kind<URI, C, S, R, E, number>) => Kind<URI, C, S, R, E, number>
function addOne<C>(F: Functor<["F_"], C>): (fa: F_<number>) => F_<number> {
return F.map((n) => n + 1)
}
하지만 이제 우리는 할 수 있다.export const functorEitherOption: Functor<["Either", "Option"], {}> = {
URI: ["Either", "Option"],
map: (f) => (fa) =>
fa._tag === "Left"
? { _tag: "Left", left: fa.left }
: fa.right._tag === "None"
? { _tag: "Right", right: { _tag: "None" } }
: { _tag: "Right", right: { _tag: "Some", value: f(fa.right.value) } }
}
다시 인덱스가 없는 상황에서, 우리는 조합 형식의 실례를 만들었다.그것이 유효하다는 것을 증명하기 위해서 우리는 우리의 Kind
를 사용할 수 있다.// <S, R, E>(fa: Either<E, Option<number>>) => Either<E, Option<number>>
const addOneEitherOption = addOne(functorEitherOption)
너무 많은 환영 유형을 제외하고 우리가 원하는 것은 이 서명이다.또한
addOne
매개 변수를 사용하여 매개 변수의 방차를 인코딩하고 주석 방차와 관련된 일반적인 매개 변수를 혼합하는 실용 프로그램 유형을 정의한다.코드를 보십시오https://github.com/Matechs-Garage/matechs-effect!
본문 코드 발췌: https://gist.github.com/mikearnaldi/7388dcf4eda013d806858d945c574fbb
Reference
이 문제에 관하여(TS4에서 HKT 인코딩1), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/matechs/encoding-hkts-in-ts4-1-1fn2텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)