提问人:Shnd 提问时间:10/22/2023 最后编辑:Shnd 更新时间:10/26/2023 访问量:53
HKT在fp-ts中是如何运作的?
How HKT really works in fp-ts?
问:
查看下面的代码。我还将其添加到打字稿游乐场的此链接中。我把部分 fp-ts 收集在一个地方,以测试和了解 HKT 的工作原理并在那里进行模拟。
type Identity<A> = { _tag: 'Identity', a: A }
// --------------------
type Option<A> = Some<A> | None
interface Some<A> {
_tag: 'Some'
value: A
}
interface None {
_tag: 'None'
}
const some = <A,>(x: A): Option<A> =>
({ _tag: 'Some', value: x })
const none: Option<never> =
{ _tag: 'None' }
const isNone = <A,>(x: Option<A>): x is None =>
x._tag === 'None'
// --------------------
type Either<E, A> = Left<E> | Right<A>
interface Left<E> {
_tag: 'Left'
left: E
}
interface Right<A> {
_tag: 'Right'
right: A
}
const left = <E,A=never>(x: E): Either<E, A> => ({ _tag: 'Left', left: x})
const right = <A,E=never>(x: A): Either<E, A> => ({ _tag: 'Right', right: x})
const isLeft = <E, A>(a: Either<E, A>): a is Left<E> =>
a._tag === 'Left'
// --------------------
interface URItoKind1<A> {
'Identity': Identity<A>
'Option': Option<A>
}
interface URItoKind2<E,A> {
'Either': Either<E,A>
}
type URIS1 = keyof URItoKind1<any>
type URIS2 = keyof URItoKind2<any, any>
type Kind1<URI extends URIS1, A> = URItoKind1<A>[URI]
type Kind2<URI extends URIS2, E, A> = URItoKind2<E,A>[URI]
// type HKT1<URI, A> = { URI: URI; a: A }; // FROM FP-TS
type HKT1<URI, A> = { What: string } // HERE IS CONFUSING TO ME. THIS COMPILES
// type HKT2<URI, A, B> = { URI: URI; a: A; b: B }; // FROM FP-TS
type HKT2<URI, A, B> = { Hello: number } // HERE IS CONFUSING TO ME. THIS COMPILES
interface Functor1<F extends URIS1> {
map: <A, B>(fa: Kind1<F, A>, f: (a: A) => B) => Kind1<F, B>
}
interface Functor2<F extends URIS2> {
map: <E, A, B>(fa: Kind2<F, E, A>, f: (a: A) => B) => Kind2<F, E, B>
}
interface Functor<F> {
map: <A, B>(fa: HKT1<F, A>, f: (a: A) => B) => HKT1<F, B>
}
// --------------------------
const optionFunctor: Functor1<'Option'> = {
map: <A,B>(fa: Option<A>, f: (x: A) => B): Option<B> =>
isNone(fa) ? none : some(f(fa.value))
}
const eitherFunctor: Functor2<'Either'> = {
map: <E,A,B>(fa: Either<E, A>, f: (x: A) => B): Either<E, B> =>
isLeft(fa) ? fa : right(f(fa.right))
}
// ---------------------------
function lift<F extends URIS2>(F: Functor2<F>): <A, B>(f: (a: A) => B) => <E>(fa: Kind2<F, E, A>) => Kind2<F, E, B>
function lift<F extends URIS1>(F: Functor1<F>): <A, B>(f: (a: A) => B) => (fa: Kind1<F, A>) => Kind1<F, B>
function lift<F>(F: Functor<F>): <A, B>(f: (a: A) => B) => (fa: HKT1<F, A>) => HKT1<F, B> // IF YOU COMMENT THIS TYPESCRIPT WILL NOT BE HAPPY, BUT WHY IT WORKS IN THE FIRST PLACE!
{
return (f) => (fa) => F.map(fa, f)
}
const liftOption = lift(optionFunctor)
const liftEither = lift(eitherFunctor)
const increment = (x: number) => x+1
console.log(liftOption(increment)(some(12)))
console.log(liftEither(increment)(right(12)))
这编译正常,运行正常。
我把 和 改成完全无稽之谈的东西HKT1
HKT2
type HKT1<URI, A> = { URI: URI; a: A };
type HKT2<URI, A, B> = { URI: URI; a: A; b: B };
自
type HKT1<URI, A> = { What: string }
type HKT2<URI, A, B> = { Hello: number }
但是代码编译正常。为什么?
更奇怪的是,如果我们评论这句话......
function lift<F>(F: Functor<F>): <A, B>(f: (a: A) => B) => (fa: HKT1<F, A>) => HKT1<F, B>
...这是重载和默认情况。代码停止编译!
香港电讯在这里如何运作?这些类型如何在 Functor 下将 Functor1 和 Functor2 粘合在一起?HKT1
答:
让我们一一介绍:
- 类型如何与 , , ...在 TypeScript 级别。
Functor
Functor1
Functor2
function lift<F extends URIS2>(F: Functor2<F>): <A, B>(f: (a: A) => B) => <E>(fa: Kind2<F, E, A>) => Kind2<F, E, B>
function lift<F extends URIS1>(F: Functor1<F>): <A, B>(f: (a: A) => B) => (fa: Kind1<F, A>) => Kind1<F, B>
function lift<F>(F: Functor<F>): <A, B>(f: (a: A) => B) => (fa: HKT1<F, A>) => HKT1<F, B>
当您与:lift
optionFunctor
const liftOption = lift(optionFunctor)
// ^? function lift<"Option">(F: Functor1<"Option">): <A, B>(f: (a: A) => B) => (fa: Option<A>) => Option<B>
使用第二个重载签名,因为是Functor1
optionFunctor
Functor1
当您与:lift
eitherFunctor
const liftEither = lift(eitherFunctor)
// ^? function lift<"Either">(F: Functor2<"Either">): <A, B>(f: (a: A) => B) => <E>(fa: Either<E, A>) => Either<E, B>
将第一个重载签名与 because is 一起使用Functor2
eitherFunctor
Functor2
TS Playground 与我添加的评论 - https://tsplay.dev/WYylvW
- 对于函数重载,我无法解释为什么注释最后一个重载(带有 和 的重载)会为其他重载创建错误。
lift
Functor
HKT
函数重载应该有实现签名,因此每个重载都会从它缩小范围(最后)。
但你也需要记住,你的是连接到:HKT1
Functor
type HKT1<URI, A> = { What: string }
interface Functor<F> {
map: <A, B>(fa: HKT1<F, A>, f: (a: A) => B) => HKT1<F, B>
}
因此,由于您没有任何通用约束:<F>
function lift<F>(F: Functor<F>): <A, B>(f: (a: A) => B) => (fa: HKT1<F, A>) => HKT1<F, B>
它仍然是正确的 TypeScript 代码,即使它映射到您意想不到的内容。
但是,当您将其注释掉时,您开始出现类型错误,例如Argument of type 'Functor1<"Option">' is not assignable to parameter of type 'Functor2<"Either">'
这里重要的一点是“实现签名也必须与重载签名兼容”。当您删除实现签名并使 second 重载实现签名时,它会破坏此规则,因此 中的函数不再具有公分母(它是 )。fa
map
HKT1<F, A>
你可以通过删除来证明这一点,然后把它作为泛型重载,类型错误就消失了:HKT1
Functor
function lift<F>(F: { map: <A, B>(fa: {}, f: (a: A) => B) => {}}): <A, B>(f: (a: A) => B) => (fa: {}) => {}
带有我更改的 TypeScript Playground - https://tsplay.dev/NaZREm
- 为什么将类型定义更改为某些随机对象类型不会导致编译错误。
HKT1
在实现签名(带有 和 的那个)处没有通用约束,因此您不会收到类型错误Functor
HKT1
实现的签名从外部不可见。编写重载函数时,应始终在函数实现上方具有两个或多个签名。
如果您有任何其他问题,请告诉我,很乐意解释
上一个:FP-TS:管道内的功能被跳过
下一个:具有任一返回类型的接收类型错误
评论
HKT1
lift
lift(optionFunctor)
optionFunctor
Functor1
Functor1
URItoKind1
Functor1
HKT1
HKT1
optionFunctor
lift