替换通用接口类型参数 [英] Replace Generic Interface Type Parameter

查看:35
本文介绍了替换通用接口类型参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为函子映射创建一个通用函数接口,它尊重所提供的接口.在下面显示的代码中,我希望 mb 的值属于 Maybe 类型,而不是实际类型 Functor.

I am attempting to create a generic function interface for functor map, that respects the interface provided. In the code shown below, I would like the value of mb to be of type Maybe<number>, as opposed to the actual type Functor<number>.

我确实意识到一种可能的解决方案是向接口FMap 添加一个重载.我对这个解决方案不满意的原因是我希望这段代码驻留在一个包中,允许用户为 Functor 创建实现,并在使用函数 时具有我上面描述的行为>地图.

I do realize that one possible solution is to add an overload to the interface FMap. The reason I am not happy with this solution is that I would like this code to reside in a package, allowing users to create implementations for Functor, and have the behavior I described above when using the function map.

interface Functor<A> {
  map<B>(fn: (a: A) => B): Functor<B>;
}

interface FMap {
  <A, B>(fn: (a: A) => B, Fa: Functor<A>): Functor<B>;
}

const map: FMap = (fn, Fa) => (
  Fa.map(fn)
);

class Maybe<A> implements Functor<A> {
  constructor(private readonly a: A) {}
  map<B>(fn: (a: A) => B): Maybe<B> {
    return new Maybe<B>(fn(this.a));
  }
}


const sqr = (x: number) => x*x;
const ma = new Maybe(5);
const mb = map(sqr, ma);

我想要一些表达以下语义的方法:

I would like some means of expressing the following semantics:

// Theoretical Code

interface PretendFMap {
  <A, B, FA extends Functor<A>>(fn: (a: A) => B, Fa: FA): FA extends (infer F)<A> ? F<B> : never;
}

然而,这不起作用,作为通用接口,没有类型参数不是有效的 TypeScript 类型,即像 Functor 这样的接口需要类型参数才能被视为类型,Functor 本身不是有效类型.

This however does not function, as a generic interface, without a type parameter is not a valid TypeScript type, i.e. an interface such as Functor requires a type parameter to be considered a type, Functor itself is not a valid type.

如果目前没有表达这些语义的方法,我们将不胜感激关于需要尽可能少的用户代码的解决方案的任何建议.

If there are currently no means of expressing these semantics, any suggestions regarding a solution that requires as little code as possible on the side of the user would be greatly appreciated.

提前感谢您的时间和考虑.

Thank you in advance for your time and consideration.

推荐答案

阻碍我们的是,当您尝试将类型变量 F 作为类型参数传递给另一个类型变量 TT 一样,即使您知道 T 实际上是一个通用接口,TS 也不允许这样做.

What stands in our way, is when you try to pass a type variable F as type parameter to another type variable T, like T<F>, TS just doesn't allow that even if you know T is in fact a generic interface.

关于这个主题的讨论可以追溯到 2014 年的 github 问题,并且它仍然是开放的,所以 TS 团队在不久的将来可能不会支持它.

There's a discussion on this topic dated back to 2014 in a github issue, and it's still open, so TS team probably won't support it in near future.

此语言功能的术语称为更高类型.使用该搜索关键字,谷歌带我去了一趟兔子洞.

The term for this language feature is called higher kinded type. Using that search keyword, google took me to a trip down the rabbit hole.

事实证明存在一个非常聪明的解决方法!

通过利用 TS 声明合并(又名模块扩充)特性,我们可以有效地定义一个空的类型存储"接口,它就像一个持有其他有用类型引用的普通对象.使用这种技术,我们能够克服这个障碍!

By leveraging TS declaration merging (aka module augmentation) feature, we can effectively define an empty "type store" interface, which acts like a plain object that holds reference to other useful types. Using this technique, we are able to overcome this blocker!

我将以您的案例为例来介绍这种技术的想法.如果您想深入了解,我会在最后附上一些有用的链接.

I'll use your case as example to cover the idea of this technique. If you want to dive deeper, I include some useful links at the end.

这是 TS Playground 链接(剧透警告强>) 到最终结果.看到它肯定.现在让我们一步一步地分解它(或者我应该说建立它?).

Here's the TS Playground link (spoiler alert) to the final result. See it in live for sure. Now let's break it down (or should I say build it up?) step by step.

  1. 首先,让我们声明一个空的 TypeStore 接口,稍后我们会更新它的内容.
  1. First, let's declare an empty TypeStore interface, we'll update it's content later.

// just think of it as a plain object
interface TypeStore<A> { } // why '<A>'? see below


// example of "declaration merging"
// it's not re-declaring the same interface
// but just adding new members to the interface
// so we can amend-update the interface dynamically
interface TypeStore<A> {
  Foo: Whatever<A>;
  Maybe: Maybe<A>;
}

  1. 我们也获取keyof TypeStore.请注意,随着 TypeStore 的内容更新,$keys 也会相应更新.
  1. Let's also get the keyof TypeStore. Noted that as content of TypeStore gets updated, $keys also get updated accordingly.

type $keys = keyof TypeStore<any>

  1. 现在我们使用实用程序类型修补缺失的语言特性高级类型".

// the '$' generic param is not just `string` but `string literal`
// think of it as a unique symbol
type HKT<$ extends $keys, A> = TypeStore<A>[$]

// where we mean `Maybe<A>`
// we can instead use:
HKT<'Maybe', A>  // again, 'Maybe' is not string type, it's string literal

  1. 现在我们有了合适的工具,让我们开始构建有用的东西.

interface Functor<$ extends $keys, A> {
  map<B>(f: (a: A) => B): HKT<$, B>
}

class Maybe<A> implements Functor<'Maybe', A> {
  constructor(private readonly a: A) {}
  map<B>(f: (a: A) => B): HKT<'Maybe', B> {
    return new Maybe(f(this.a));
  }
}

// HERE's the key!
// You put the freshly declare class back into `TypeStore`
// and give it a string literal key 'Maybe'
interface TypeStore<A> {
  Maybe: Maybe<A>
}

  1. 最后FMap:

// `infer $` is the key here
// remember what blocked us? 
// we cannot "infer Maybe from T" then apply "Maybe<A>"
// but we can "infer $" then apply "HKT<$, A>"!
interface FMap {
  <A, B, FA extends { map: Function }>
  (f: (a: A) => B, fa: FA): FA extends HKT<infer $, A> ? HKT<$, B> : any
}

const map: FMap = (fn, Fa) => Fa.map(fn);

<小时>

参考

  1. 关于在 TS 中支持 higer 类型的 github 讨论
  2. 兔子洞入口
  3. 声明合并在 TS 手册中
  4. 关于更高级类型的帖子
  5. @gcanti 中等帖子, 在 TS 中更高级的类型
  6. fp-ts 库,@gcanti
  7. hkts 库,@pelotom
  8. typeprops 库由 @SimonMeskens 提供
  1. The github discussion on supporting higer kinded type in TS
  2. Entrance to the rabbit hole
  3. Declaration Merging in TS Handbook
  4. SO post on higher kinded type
  5. Medium post by @gcanti, on higher kinded types in TS
  6. fp-ts lib by @gcanti
  7. hkts lib by @pelotom
  8. typeprops lib by @SimonMeskens

这篇关于替换通用接口类型参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆