Typescript: 允许类在其他参数类中是参数性的

创建于 2014-11-19  ·  140评论  ·  资料来源: microsoft/TypeScript

这是允许泛型用作类型参数的建议。 目前可以编写monad的特定示例,但是为了编写所有monad都满足的接口,我建议编写

interface Monad<T<~>> {
  map<A, B>(f: (a: A) => B): T<A> => T<B>;
  lift<A>(a: A): T<A>;
  join<A>(tta: T<T<A>>): T<A>;
}

同样,可以编写笛卡尔函子的特定示例,但是为了编写所有笛卡尔函子都满足的接口,我建议编写

interface Cartesian<T<~>> {
  all<A>(a: Array<T<A>>): T<Array<A>>;
}

参数类型参数可以采用任意数量的参数:

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

也就是说,当类型参数后面跟随有波浪号和自然Arity时,应在声明的其余部分中将类型参数与给定Arity一起用作泛型类型。

与现在一样,在实现这样的接口时,应填写通用类型参数:

class ArrayMonad<A> implements Monad<Array> {
  map<A, B>(f: (a:A) => B): Array<A> => Array<B> {
    return (arr: Array<A>) =>  arr.map(f);
  }
  lift<A>(a: A): Array<A> { return [a]; }
  join<A>(tta: Array<Array<A>>): Array<A> {
    return tta.reduce((prev, cur) => prev.concat(cur));
  }
}

除了直接允许在参数中包含泛型类型之外,我还建议typedef还支持以这种方式定义泛型(请参见问题308 ):

typedef Maybe<Array<~>> Composite<~> ;
class Foo implements Monad<Composite<~>> { ... }

定义的别名和别名必须匹配,才能使typedef有效。

Suggestion help wanted

最有用的评论

HKT的思维方式可以改变,习惯改变,失去的后代复活,这将是自泛型和显式null和undefineds以来最大的事情,它可以改变一切

请考虑将其作为下一个重要功能,不要再听那些不断问您要更好的马的人了,给他们AF * G法拉利

所有140条评论

不要做任何轻率的假设,但我认为您输入的是错误的。 所有参数类型都需要参数名称,因此您可能要输入

map<A, B>(f: (x: A) => B): T<A> => T<B>;

而现在map是一个将映射器从类型any (其中参数名称为A )转换为B函数。

尝试使用--noImplicitAny标志获得更好的结果。

谢谢,改正。

我已将我的评论更新为提案。

:+1:更高种类的类型对于函数式编程构造将是一个很大的好处,但是在此之前,我希望对高阶函数和泛型:p有正确的支持

准批准。

我们非常喜欢这个想法,但是需要一个可行的实现方案来尝试理解所有的影响和潜在的极端情况。 拥有一个至少可以解决80%用例的示例PR,对于下一步真的很有帮助。

人们对波浪号语法有何看法? T~2的替代品可能是这样的

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

允许直接组成泛型而不需要类型别名:

interface Foo<T<~,~,~>, U<~>, V<~, ~>> {
  bar<A, B, C, D>(a: A, f: (b: B) => C, d: D): T<U<A>, V<B, C>, D>;
}

因为我们在其他任何地方都没有这样做,所以拥有明确的友善很奇怪。

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

但是,我知道其他语言在相似的上下文中使用*而不是~更加清晰一些:

interface Foo<T<*,*>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

尽管将这一观点推到了极点,但您可能会得到:

interface Foo<T: (*,*) => *> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

我认为T<~,~>也比T~2更清晰。 我将修改上面的提案。 我不在乎我们使用~还是* ; 它只是不能成为JS标识符,所以我们不能使用_ 。 我看不出=>标记有什么好处; 所有泛型都采用某些输入类型,并返回单个输出类型。

轻量级的语法将完全避免泛型的麻烦。 解析器会从第一次使用中找出来,如果其余部分与之不一致,则会抛出错误。

我很乐意开始实施此功能。 对于缠扰开发人员的有关编译器实施细节的推荐论坛是什么?

您可以使用更多的相关代码示例记录许多新问题,以解决较大的问题,也可以记录一系列问题,解决长期问题。 另外,您也可以在这里加入聊天室,网址为https://gitter.im/Microsoft/TypeScript ,我们可以在那里聊天。

@metaweta有什么消息吗? 如果您需要任何帮助/讨论,我将很高兴就此问题进行集思广益。 我真的很想要这个功能。

不,工作中的工作占用了我必须从事的空闲时间。

凹凸:是否有机会看到曾经考虑过的功能?

https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -96854288仍然是它的当前状态。 我在这里看不到任何会使我们更改功能优先级的东西。

在我看来,这比导入类别理论抽象在更多情况下有用。 例如,能够编写将Promise实现(构造函数)作为参数的模块工厂,例如具有可插入promise实现的数据库,这将是有用的:

interface Database<P<~> extends PromiseLike<~>> {   
    query<T>(s:string, args:any[]): P<T> 
}

在这里也将派上用场

:+1:

HKT的思维方式可以改变,习惯改变,失去的后代复活,这将是自泛型和显式null和undefineds以来最大的事情,它可以改变一切

请考虑将其作为下一个重要功能,不要再听那些不断问您要更好的马的人了,给他们AF * G法拉利

是的,在尝试将类型添加到现有JS代码库后的前15分钟就碰到了这一点。 在看到它之前,我不会切换到TS。

我可以帮忙吗?

我不知道这与#7848有什么关系? 它们非常相似,尽管关于高阶种类的其他方面。

@ boris-marinov Ryan Cavanaugh的回复说您可以:

拥有一个至少可以解决80%用例的示例PR,对于下一步真的很有帮助。

现在,我有时间实现这样一个简单的PR Hope,以从核心开发人员那里获得一些提示,但是到目前为止,还没有任何问题-一切看起来都不错并且可以理解。 将在这里跟踪进度。

@Artazor您是否也

我认为这个建议绝对是美好的。 在TypeScript中拥有更高种类的类型将使它达到一个全新的水平,在这里我们可以描述比当前可能更强大的抽象。

但是,OP中提供的示例是否有问题? 行中的A

class ArrayMonad<A> implements Monad<Array> {

在所有方法中均未使用,因为它们都有自己的通用A

另外,如果使用map作为使用this的方法来实现functor会是什么样? 这样吧?

interface Functor<T, A> {
  map<B>(f: (a: A) => B): T<A> => T<B>;
}

class Maybe<A> implements Functor<Maybe, A> {
  ...
}

@paldepind退房#7848。 该讨论是关于该特定用例的,尽管恕我直言,并且确实需要将其合并到单个PR中。

这些东西什么时候会降落? 这似乎是必不可少的。

也将使这样成为可能:

interface SomeX<X, T> {
   ...// some complex definition
  some: X<T>
}

interface SomeA<T> extends SomeX<A, T> {
}

@whitecolor我认为目前要炸的鱼更大,值得优先考虑:

  1. TypeScript 2.0 RC仅在不到两周前发布。 这本身将花费很多时间。
  2. 未键入本地JS函数bindcallapply 。 这实际上取决于可变参数的仿制药提议Object.assign也需要类似的解决方法,但是仅可变参数泛型不能解决该问题。
  3. 诸如Lodash的_.pluck ,Backbone模型的getset方法等功能目前尚未输入类型,而解决此问题基本上使Backbone可更安全地与TypeScript一起使用。 将来

并不是说我不想使用此功能(我会_爱用这样的功能),我只是认为它不会很快推出。

@isiahmeadows
感谢您的解释。 是的,列表中的第三项非常重要,也要等待https://github.com/Microsoft/TypeScript/issues/1295

但是我希望以某种方式在2.1dev中解决当前问题。

我同意。 希望它可以加入。

(此问题想要的等级2多态性也是
Fantasy Land用户,请在该规范中正确键入各种ADT。
Ramda是一个非常需要此库的很好的例子。)

2016年9月6日,星期二,11:00亚历克斯[email protected]写道:

@isiahmeadows https://github.com/isiahmeadows
感谢您的解释。 是的,列表中的第三项非常重要,
等待#1295 https://github.com/Microsoft/TypeScript/issues/1295
太。

但是我希望以某种方式在2.1dev中解决当前问题。

-
您收到此邮件是因为有人提到您。

直接回复此电子邮件,在GitHub上查看
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -244978475,
或使线程静音
https://github.com/notifications/unsubscribe-auth/AERrBMvxBALBe0aaLOp03vEvEyokvxpyks5qnX_8gaJpZM4C99VY

似乎此功能将有助于我们定义反应形式。 例如,您具有struct:

interface Model {
  field1: string,
  field2: number,
  field3?: Model
}

我有一个处理程序,定义为:

interface Handler<T> {
  readonly value: T;
  onChange: (newValue: T) => void;
}

该处理程序作为支持传递给React组件。 我还有一个函数,该函数接受struct并返回相同的struct,但是使用Handlers而不是value:

function makeForm(value: Model): {
  field1: Handler<string>,
  field2: Handler<number>,
  field3: Handler<Model>,
}

到目前为止,我无法正确键入该函数,因为TS无法基于其他类型的结构产生类型。

我可以用HKT键入makeForm吗?

嗯,有趣。

也许可能是这样的:

//Just a container
interface Id <A> {
  value: A
}

interface Model <T> {
  field1: T<string>,
  field2: T<number>,
  field3?: T<Model>
}

makeForm (Model<Id>): Model<Handler>

@ boris-marinov最有趣的一点是这一行:

interface Model<T> {
  //...
  field3?: T<Model> // <- Model itself is generic.
                    // Normally typescript will error here, requiring generic type parameter.
}

也许值得一提的是,HKT可以解决所谓的部分类型(https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155):

type MyDataProto<K<~>> = {
    one: K<number>;
    another: K<string>;
    yetAnother: K<boolean>;
}
type Identical<a> = a;
type Optional<a> = a?; // or should i say: a | undefined;
type GettableAndSettable<a> = { get(): a; set(value: a): void }

type MyData = MyDataProto<Identical>; // the basic type itself
type MyDataPartial = MyDataProto<Optional>; // "partial" type or whatever you call it
type MyDataProxy = MyDataProto<GettableAndSettable>; // a proxy type over MyData
// ... etc

不完全的。 {x: number?}无法分配给{x?: number} ,因为一个
保证存在,而另一个不存在。

2016年10月11日,星期二,09:16 Aleksey Bykov [email protected]写道:

也许值得一提的是HKT可能是所谓的答案
部分类型(#4889(评论)
https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155
):

键入MyDataProto 一:K;
另一个:K;
另一个:K;
} type Identical = a; type Optional = a ?;
= {get():a;

;
;
;

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub上查看
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -252913109,
或使线程静音
https://github.com/notifications/unsubscribe-auth/AERrBNFYFfiW01MT99xv7UE2skQ3qiPMks5qy4wRgaJpZM4C99VY

@isiahmeadows你是对的,目前没有办法/语法使仅基于其类型的属性真正可选,这真是可惜

还有一个:如果可以将财产设为readonly那将是一个好

只是把它扔在那里...我更喜欢*语法而不是~语法。 从键盘布局的角度来看,〜似乎还很遥远。 另外,我不确定为什么,但是我认为*在混合使用的所有尖括号中似乎更具可读性/区分性。 更不用说,熟悉Haskell等其他语言的人可能会立即将语法与HKT关联。 似乎更自然。

我必须同意*语法。 首先,它更具区别性,
其次,它更好地表示“任何类型的作品”类型。


伊西亚·梅多斯(Isiah Meadows)
[email protected]

2016年11月6日星期日,12:10 AM,Landon Poch [email protected]
写道:

只是把它扔在那里...我更喜欢*语法而不是〜语法。
关于〜的东西似乎与键盘布局相去甚远
透视。 另外,我不确定为什么,但是我认为*似乎更多
混合中的所有尖括号都可读/可区分。
更不用说,熟悉Haskell等其他语言的人可能
立即将语法与HKT关联。 似乎更自然。

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub上查看
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -258659277,
或使线程静音
https://github.com/notifications/unsubscribe-auth/AERrBHQ4SYeIiptB8lhxEAJGOYaxwCkiks5q7VMvgaJpZM4C99VY

里程碑: community吗? 此问题/功能的当前状态是什么?

@whitecolor状态为DIY(自己动手做)

该问题带有Accepting PRs标签。 这意味着欢迎请求实现此功能。 有关更多详细信息,请参见https://github.com/Microsoft/TypeScript/wiki/FAQ#what -do-the-labels-on-these-issues-mean。

另请参阅https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -96854288

好的,我看到这些标签了,只是怀疑非TS团队是否有能力做到这一点。

现在,我有时间实现这样一个简单的PR Hope,以从核心开发人员那里获得一些提示,但是到目前为止,还没有任何问题-一切看起来都不错并且可以理解。 将在这里跟踪进度。

@Artazor你有运气吗?

@raveclassic-看起来比看起来困难得多,但是我仍然希望继续前进。 从语法上讲这很明显,但是类型检查规则/阶段对我来说不是我想要的那么清晰-)

让我们尝试恢复我的活动-)

只是跟踪进度以及想法发展的路径。 我考虑了三个选项来实现此功能。

我计划通过可选的higherShape属性来丰富TypeParameterDeclaration

    export interface TypeParameterDeclaration extends Declaration {
        kind: SyntaxKind.TypeParameter;
        name: Identifier;
        higherShape?: HigherShape // For Higher-Kinded Types <--- this one 
        constraint?: TypeNode;

        // For error recovery purposes.
        expression?: Expression;
    }

并考虑了三个选项HigherShape实现方式

1.领域的简单团结

type HigherShape = number

它对应于以下用法:

class Demo<Wrap<*>, WrapTwo<*,*>> {   // 1 and 2
    str: Wrap<string>;
    num: Wrap<number>;
    both: WrapTwo<number, string>;
}

在这种最简单的情况下,看起来number类型就足够了。 但是,我们应该能够为每个给定类型确定实际的upperShape,以确保我们可以将其用作特定形状要求的类型参数。 这里我们面临一个问题: Demo类本身的较高形状不能用数字表示。 如果可以,则应将其表示为2 -因为它有两个类型参数,
而且有可能写

var x: Demo<Array, Demo>

然后与属性.both的延迟类型检查问题作斗争。 因此, number类型是不够的(我相信);

实际上,类型Demo具有以下高阶形状:

(* => *, (*,*) => *) => *

2.完全结构化的域和共同域

然后,我研究了较高形状的相反,最完整的表示形式,这将允许表示上述形状,甚至更糟:

(* => (*,*)) => ((*,*) => *)

数据结构很简单,但是与TypeScript类型系统不能很好地相互作用。 如果我们允许这样的高阶类型,那么我们将永远不会知道*意味着地面类型,该地面类型可以用于值的类型化。 此外,我什至没有找到合适的语法来表达如此高阶的约束。

3.结构化域/隐式简单共同域

主要思想-类型表达式(即使带有实际的类型参数)始终会导致基本类型-可用于键入变量。 另一方面,每个类型参数可以具有自己的详细类型参数,其详细类型参数与其他地方使用的格式相同。

这是我最后要提倡的决定。

type HigherShape = NodeArray<TypeParameterDeclaration>;

例:

class A {x: number}
class A2 extends A { y: number }
class Z<T> { z: T; }

class SomeClass<T1<M extends A> extends Z<M>, T2<*,*<*>>, T3<* extends string>> {
        var a: T1<A2>; // checked early
        var b: T2<string, T1>; // second argument of T2 should be generic with one type parameter  
        var c: T3<"A"|"B">; // not very clever but it is checked
        // ...
        test() {
             this.a.z.y = 123 // OK
             // nothing meaningful can be done with this.b and this.c
        }
}

在这里,我想指出, MT1<M extends A> extends Z<M>本地文件,并且存在于比T1更深的可见性范围内。 因此, SomeClass主体中没有M
*只是意味着一个永不冲突的新标识符(匿名类型)(可以在以后阶段实现)


因此,TypeParameterDeclaration的最终签名

    export interface TypeParameterDeclaration extends Declaration {
        kind: SyntaxKind.TypeParameter;
        name: Identifier;
        typeParameters?: NodeArray<TypeParameterDeclaration> // !!! 
        constraint?: TypeNode;

        // For error recovery purposes.
        expression?: Expression;
    }

想听@DanielRosenwasser的任何意见,@阿列克谢-贝科夫,@isiahmeadows和其他- )

听起来不错,但我对TypeScript代码库的内部结构了解甚少。

想要在要求此的合唱团中加入我的声音,并为您加油,Artazor! :)

在我实现Redux类型安全的实现中,此功能对我很有用。

@michaeltontchev使Redux类型安全有什么问题?

如果您有兴趣,我最近发布了https://github.com/bcherny/tduxhttps://github.com/bcherny/typed-rx-emitter ,它们基于Redux和EventEmitter的思想。

现在看起来,需要使用默认的通用参数重新设置为@rbuckton分支#13487。 在其他情况下,我们将在很大程度上发生冲突。

@bcherny-感谢您的链接,我将检查它们!

我一直在研究如何确保CombineReducers具有类型安全性,方法是确保该州针对每个属性(没有额外功能)都具有正确类型的reducer。 在没有嵌套泛型的情况下,我设法做到了这一点,但是嵌套解决方案会更好。 我有以下内容:

import { combineReducers, Reducer } from 'redux';

interface IState {
    // my global state definition
}

type StatePropertyNameAndTypeAwareReducer\<S> = {
    [P in keyof S]: Reducer<S[P]>;
};

let statePropertyToReducerMap : StatePropertyNameAndTypeAwareReducer<IState> = {
    navBarSelection: navBarReducer,
};

let combinedReducers = combineReducers<IState>(statePropertyToReducerMap);

基本上,我上面介绍的类型可以确保传递给CombineReducers的化简器映射覆盖状态的每个属性,并具有正确的返回类型。 我在网上搜索时找不到任何这样的解决方案-在我看来,如果没有keyof功能就无法完成,该功能仅在两个月前才推出:)

我希望keyof功能也将对ImmutableJ派上用场,以使setter和getter成为类型安全的,尽管您可能仍需要一些其他工具来解决。

编辑:澄清一下,嵌套泛型将使我不必在StatePropertyNameAndTypeAwareReducer类型中硬编码Reducer类型,而是将其作为泛型传递,但它需要是嵌套泛型,这在此时此刻。

Edit2:在这里为Redux创建了一个问题: https :

相关: https :

最近好吗?

也许是一个幼稚的问题,但是为什么要用~*而不是常规的泛型参数呢? 是否表示未使用? 就是为什么不:

type Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

要么:

kind Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

甚至:

abstract type Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

@bcherny我认为这会引起语法上的歧义,因为Functor<A<T>>以前的意思是“ A of T ”,其中T是本地的某种类型范围。 不太可能,但是出于相同的原因,对于某些代码库,此语法也可能最终成为一项重大更改。

@masaeedu我明白了。 新语法的意思是“懒惰地绑定T ”,而不是“严格地在当前范围内绑定T ”。

就是说,我认为@DanielRosenwasserT: * => *提议在这里是最合理的,因为它具有“先有技术”。

在Haskell中,运算符->实际上是参数化的类型(也许更容易可视化Func<TArg, TRet> )。 类型构造函数->接受两个任意类型TU并生成将T类型的值映射到的值构造函数(即函数)的类型U类型的值。

有趣的是,它也是一种友好的构造函数! 种类构造函数->接受两个任意种类的T*U* (仅用于视觉区分的星号),并产生一种类型构造函数,该类型构造函数映射类型为T*类型U*

您可能会在此时注意到一种模式。 用于定义和引用类型的语法和语义可以简单地重用于定义和引用类型。 实际上,它甚至没有被重用,只是一次隐式地在两个不同的Universe中定义事物。 (事实上​​,它是同构的,这意味着它能够以无限的水平,值->类型->类型->排序-> ...定义事物,但不幸的是* ,但是那是一个不同时间的主题)

实际上,这种模式非常有意义,以至于有些人实现了广泛使用的GHCi扩展,将其推广到所有类型的构造函数,而不仅仅是-> 。 扩展名为“数据种类”,这就是Haskell如何通过其异构列表( []既是值列表的类型,又是类型列表的类型),异构元组,“智能长度向量以及其他许多功能。

也许我们现在还不想达到DataKinds地步,所以我们只会坚持使用*->这样的构造函数,但是如果我们遵循Daniel提出的语法,或更笼统地说使种类定义与类型定义同构,我们开放自己以利用这一领域的未来发展。

在我之前的漫谈之后,我建议我们使用any代替* ; 这既代表每个值的类型,又代表每种类型的类型。 如果语法看起来令人困惑,我们可以从Haskell的书中取出一页,并使用'前缀消除种类和类型的歧义。

OP的示例将如下所示:

interface Monad<(T: 'any => 'any)> {
    // ...
}

Nitpick:我发现any通常会造成两种不同的作用,令人感到困惑。
它是所有其他类型的超级类型,就像从来没有其他所有类型的子类型一样,因此,如果函数要求使用any参数,则可以放入任何内容。 到现在为止还挺好。
有趣的是,当一个函数要求特定的东西时,您提供any 。 这种类型检查,而其他所有比要求的类型都要宽的类型都会使它出错。
但是,是的。

另一方面, '会令人困惑,因为它也用于字符串文字中。

@Artazor有任何新闻吗? 上次提到时,您需要基于默认的通用参数。 在我看来,您是唯一接近工作中的POC的人。

还值得考虑这与子类型如何相互作用。 仅使用*是不够的; 在使用即席多态而不是有界多态的语言中,您具有用于限制可接受类型参数的约束类型。 例如, Monad T类型实际上是Constraint ,而不是*

在TypeScript中,我们改用结构子类型,因此我们的种类需要反映类型之间的子类型关系。 关于该主题的Scala论文可能对如何在一种类型的系统中表示方差和子类型关系提出了一些好主意:“实现更高类型的平等权利”。

有什么进展吗?

@gcanti的替代方法https://medium.com/@gcanti/higher -kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

fp-ts采用的方法的问题在于,它使您可以重新实现原本经过验证的库。 对我来说,打字稿的想法是能够正确地键入当前被认为是JavaScript最佳实践的内容,而不是强迫您以某种方式重新实现它。

这里有很多示例表明需要HKT来正确描述我们目前在js库中使用的合同,无论是幻想土地,ramda还是react形式。

看到这种情况真的很高兴:)

〜是否有人愿意/有能力从事这项有偿工作? 请随时与我联系讨论。 或者任何人都可以指导我们可能会从事的人,请也让我知道。〜[编辑:我[可能已决定] -357567767)放弃这个生态系统,我随后在此主题中发表的评论使我意识到这可能是一项艰巨的任务]

@gcanti的替代方法https://medium.com/@gcanti/higher -kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

我没有费心去理解,因为我观察到生成的map仍显式指定了Option容器类型,因此并不像泛型类型(HKT)那样完全通用可以提供:

function map<A, B>(f: (a: A) => B, fa: HKTOption<A>): Option<B> {
  return (fa as Option<A>).map(f)
}

正如@spion在2016年8月26日指出的那样,HKT对于使需要工厂的任何函数都具有泛型功能是必需的,在这种功能中,参数化容器类型本身就是泛型的。 我们在关于编程语言设计的讨论中已经探讨了这一点

PS:如果您感到好奇,那么此功能会成为我(包括@keean的)对编程语言前景的分析的重要因素。

@ shelby3 FWIW Optionmap (在Option.ts )不是通用的,因为代表实例,Functormap (在Functor.ts )是泛型的,因为它表示类型class 。 然后,您可以定义一个通用的lift函数,该函数可以与任何仿函数实例一起使用。

看到这种情况真的很高兴:)

我非常同意:)

@ shelby3 :要合并这样的功能,最好的选择是
他们在TS路线图上对其进行优先排序; 我有一些主要的公关
反馈/合并,无论是小的修补程序还是已经在寻找
进入他们。 我不想消极,但这是你要考虑的
即将为此投入资源。

在2018年1月8日下午4:05,“ shelby3” [email protected]写道:

有没有人愿意/有能力从事这笔薪水? 随时联系
[email protected]讨论。 或者任何人都可以指导某人
我们可能会对此进行处理,也请让我知道。

@gcanti的替代方法https://github.com/gcanti
https://medium.com/@gcanti/higher -kinded-types-in-
打字稿静态和幻想的土地d41c361d0dbe

我没有费心去完全理解,因为我观察了结果图
仍然明确指定Option容器类型,因此不完全
更高种类的类型(HKT)可以提供的通用:

功能图(f:(a:A)=> B,fa:HKTOption ):选项{ return(fa作为Option ).map(f) }

HKT对于使需要工厂的任何功能通用化是必需的
其中参数化容器类型本身就是通用的。 我们有
在我们的文章中探索了https://github.com/keean/zenscript/issues/10
有关编程语言设计的讨论。

PS:如果您很好奇,此功能会在很大程度上影响我
(包括@keean https://github.com/keean的)分析
编程语言环境
https://github.com/keean/zenscript/issues/35#issuecomment-355850515 一世
意识到我们的观点并不完全与Typescript的优先级相关
主要目标是成为Javascript / ECMAScript和支持的超集
该生态系统。

-
您收到此消息是因为您已订阅此线程。
直接回复此电子邮件,在GitHub上查看
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-355990644
或使线程静音
https://github.com/notifications/unsubscribe-auth/AC6uxYOZ0a8G86rUjxvDaO5qIWiq55-Fks5tIi7GgaJpZM4C99VY

@gcaniti对此表示歉意,并感谢您的其他解释。 在发表评论之前,我应该做更多的研究。 当然,这是我在概念化方面的错误,因为(我已经知道)函子需要实例实现。

Afaics,您的聪明“黑客”可以泛型地引用工厂(例如lift ),但是需要附加的Module Augmentation样板文件来(针对特定类型的函子类型)对通用工厂的每种类型进行(更新和)专门化。 ,例如Option 。 通用工厂的每次通用使用都不需要样板吗,例如我讨论过的通用sort示例@keean ? 可能还会发现其他极端情况吗?

Kotlin是否复制了您的想法,反之亦然? (对该链接有一些其他批评,但我不知道它们是否适用于Typescript案例)

我不想否认,但是如果您要为此投入资源,这是一个考虑因素。

是的,我也想到了这种想法。 感谢您的表述。 我怀疑其中一个考虑因素是通常对类型系统及其可能产生的任何极端情况的影响,正如@masaeedu指出的那样。 除非对此进行了彻底的思考和证明,否则可能会有抵抗。

请注意,我也在研究锡兰,以更好地确定他们对EMCAScript编译目标的投资水平。 (我需要学习更多)。

我也被这个限制所困扰。 我希望在以下示例中自动推断出I


interface IdType<T> {
  id: T;
}

interface User {
  id: number;
  name: string;
}

function doStuff<T extends IdType<I>>() {
  const recs = new Map<I, T>();
  return {
    upsert(rec: T) {
      recs.set(rec.id, rec);
    },
    find(id: I) {
      return recs.get(id);
    },
  };
}

(function () {
  const stuff = doStuff<User>();
  stuff.upsert({id: 2, name: "greg"});
  console.log(stuff.find(2));
})();

据我所知,这需要一种类型较高的类型,或者指定一个重复的泛型参数(例如doStuff<User, number>() ),这似乎是多余的。

最近,我也对此限制感到震惊。

我一直在为诺言而努力。 它提供了用于处理它们的各种实用程序功能。

该库的主要功能是它返回您放入其中的诺言类型。 因此,如果您使用Bluebird Promise并调用了其中一个功能,它将返回Bluebird Promise及其提供的所有其他功能。

我想在类型系统中对此进行编码,但是我很快意识到这需要使用类型P * -> *的类型P ,例如P<T> extends Promise<T>

这是一个此类功能的示例:

/**
* Returns a promise that waits for `this` to finish for an amount of time depending on the type of `deadline`.
* If `this` does not finish on time, `onTimeout` will be called. The returned promise will then behave like the promise returned by `onTimeout`.
* If `onTimeout` is not provided, the returned promise will reject with an Error.
*
* Note that any code already waiting on `this` to finish will still be waiting. The timeout only affects the returned promise.
* <strong i="14">@param</strong> deadline If a number, the time to wait in milliseconds. If a date, the date to wait for.
* <strong i="15">@param</strong> {() => Promise<*>} onTimeout Called to produce an alternative promise once `this` expires. Can be an async function.
*/
timeout(deadline : number | Date, onTimeout ?: () => PromiseLike<T>) : this;

在上述情况下,我可以通过使用相当笨拙的this类型来避免需要更多种类。

但是,以下情况无法解决:

/**
* Returns a promise that will await `this` and all the promises in `others` to resolve and yield their results in an array.
* If a promise rejects, the returned promise will rejection with the reason of the first rejection.
* <strong i="21">@param</strong> {Promise<*>} others The other promises that must be resolved with this one.
* <strong i="22">@returns</strong> {Promise<*[]>} The return type is meant to be `Self<T[]>`, where `Self` is the promise type.
*/
and(...others : PromiseLike<T>[]) : ExtendedPromise<T[]>;

因为没有可以让我做this<T[]>骇客。

请注意我在文档中的简短道歉。

如上述参考资料所示,在另外一种情况下,我认为此功能将很有用(假设我已经正确理解了提案)。

在所讨论的软件包中,有必要将一个无类型的泛型类或函数用作类型,因为泛型类型通常是用户创建的。

将提案应用到我的方案中,我认为它将类似于:

import { Component, FunctionalComponent } from 'preact';

interface IAsyncRouteProps {
    component?: Component<~,~> | FunctionalComponent<~>;
    getComponent?: (
        this: AsyncRoute,
        url: string,
        callback: (component: Component<~,~> | FunctionalComponent<~>) => void,
        props: any
    ) => Promise<any> | void;
    loading?: () => JSX.Element;
}

export default class AsyncRoute extends Component<IAsyncRouteProps, {}> {
    public render(): JSX.Element | null;
}

鉴于在我的实现中无法可靠地引用泛型类型,我敢肯定我错过了一些东西。

@ Silic0nS0ldier实际上,这种情况现在可以解决。 您使用结构构造函数类型,如下所示:

type ComponentConstructor = {
    new<A, B>() : Component<A, B>;
}

然后说component ?: ComponentConstructor

甚至更一般地,您实际上可以具有通用函数类型:

let f : <T>(x : T) => T

这称为n级参数多态性,实际上在语言中是非常罕见的功能。 因此,为什么TypeScript没有更高种类的类型(这是一个更为常见的功能),这令人更加困惑。

如果您需要引用特定的TComponent<T, S>则会出现此处讨论的限制。 但是对于您来说,这似乎是不必要的。


您还可以使用typeof Component来获取构造函数Component的类型,但这会导致子类型出现各种问题。

@GregRos您提出的解决方案看起来很有希望(它接受定义文件中的类型),但是兼容类型被拒绝。 https://gist.github.com/Silic0nS0ldier/3c379367b5e6b1abd76e4a41d1be8217

@ Silic0nS0ldier请参阅我对要点的评论。

@chrisdavies有用吗?

interface IdType<T> {
    id: T;
}

interface User {
    id: number;
    name: string;
}

function doStuff<T extends IdType<any>>() {
    type I = T['id']; // <==== Infer I
    const recs = new Map<I, T>();
    return {
        upsert(rec: T) {
            recs.set(rec.id, rec);
        },
        find(id: I) {
            return recs.get(id);
        },
    };
}

(function() {
    const stuff = doStuff<User>();
    stuff.upsert({ id: 2, name: "greg" });
    console.log(stuff.find(2));
})();

@ jack-williams是的。 这适用于我的情况。 我没有在文档中找到该示例(尽管我以缺少东西而闻名!)。 谢谢!

我一直在考虑此功能,并且对此事有一些想法,倾向于某种规范,但是我仍然可以看到很多问题。 我的建议与迄今为止提出的建议有些不同。


首先,我认为对类型构造函数使用任何类型的T<*, *>语法都是一个坏主意,因为它不能很好地扩展类型构造函数的复杂性。 我也不确定每次引用类型构造器的类型时是否有意义,因为我们不对函数执行此操作,即使对于具有类型参数的函数也是如此。

我认为实现此目的的最佳方法是像常规类型一样对待其他类型较高的类型,并使用常规名称,并在类型构造函数本身上定义一个好的子类型关系,该关系可用于施加约束。

我确实认为我们应该使用某种前缀或后缀来将它们与其他类型区分开,主要是为了防止用户在只想编写常规代码的情况下避免涉及类型构造函数的错误消息。 我有点像: ~Type, ^Type, &Type东西。

因此,例如,一个函数签名可能是:

interface List<T> {
    push(x : T);
}

function mapList<~L extends ~List, A, B>(list : L<A>, f : (x : A) => B) : L<B>;

(我不是故意使用~前缀作为构造类型)

通过在这里使用extends ,我基本上说了两件事:

** 1。 必要时: ~L是一种类型构造函数,具有与~List相同的类型,即类型* -> * (或* => * ,因为=>是TypeScript箭头)。

  1. ~L~List 。**的子类型。

使用extends表示类型构造函数的类型可缩放到任意复杂的类型构造函数,包括((* => *) => (* => *)) => *

您实际上在类型的标识符中看不到这种类型,但我认为您不需要。 我什至不确定类型构造函数之间的子类型关系是否必须保留种类,因此(1)可能不是必需的。

没有构造不完整的类型

我认为我们不应该支持构造不完整的类型。 也就是说,像这样:

(*, *) => * => *

我认为这会带来更多的麻烦,而不是值得的。 也就是说,每个类型构造函数都必须构造某种具体类型,并且必须在定义TC的上下文中指定具体类型。

定义类型构造函数的结构方式

我还认为应该有一种结构化的方法来指定类型构造函数,就像可以在结构上指定任何其他类型一样,包括非常高级的泛型函数类型。 我一直在思考如下语法:

~<A, B> { 
    a : A,
    b : B
}

这类似于具有类型参数的函数类型的现有语法:

<A, B>() => { a : A, b : B};

甚至可以将两者结合起来得到:

~<A><B, C> => [A, B, C]

这是构造通用函数类型的类型构造函数。

好处是,当指定其他结构类型,指定类型约束等时,可以使用这些结构类型。 有时,这意味着他们可以使用无法从其他任何地方引用的引用局部符号。

这是一个例子:

type List<A, B> = ...;

type AdvancedType<~L extends ~<A>List<A, B>, B> = ...;

在上面的示例中,结构类型构造函数~<A>List<A, B>引用类型参数B 。 至少以某种方式编码这种关系是不可能的,至少不对部分构造的类型List<A, *>进行编码。 还有其他例子。

子类型关系

子类型关系似乎很有意义,但是我在尝试表征它时遇到了许多困难。

我的第一个想法是。 对于~A~B的子类型:

  1. (a)它们必须具有相同的种类(在友善而非约束方面)。
  2. (b)对于每一个法律的参数T₁, T₂, ...~AA<T₁, T₂, ...>必须的子类型B<T₁, T₂, ...>

但是,这有几个限制。

  1. 类MySpecialPromise实现PromiseLike {} 在这种情况下, ~MySpecialPromise不是~PromiseLike的子类型,因为它们具有不同的种类。

  2. 类MyArrayPromise实现PromiseLike

    在这种情况下,子类型关系也不守恒。

以下是(b)的更广义的版本:

(b)对于每一个法律的参数T₁, T₂, ...~A ,存在一个参数S₁, S₂, ...~B使得A<T₁, T₂, ...>是的子类型B<S₁, S₂, ...>

换句话说,具有上述特性的映射F (T 1,T 2,...)= S 1,S 2,...。 必须使用此映射才能从参数化的A<...>构造参数化的B<...> A<...> 。 即使类型构造函数具有不同的种类,它也可以允许我们执行此操作。

这种关系的问题是我不确定如何找到正确的映射。 在标称类型的语言中,每条语句均遵循以下原则:

A<...> extends B<...>

~A的类型参数和~B的类型参数之间定义映射,因此可以恢复映射。 但是,在TypeScript的结构化类型系统中,我们不能依靠这种类型的显式语句。

一种方法是仅为具有正确类型信息的类型支持类型构造函数,例如implements子句或类似于Scala的某种抽象类型成员。 不过,我不确定这是否是前进的道路。

@GregRos-有趣的笔记! 几个问题。


具体类型是什么意思? 您是说类型*还是没有绑定类型参数的类型?


没有构造不完整的类型
我认为我们不应该支持构造不完整的类型。 也就是说,像这样:
(*,*)=> * => *

构造不完整类型是什么意思? 您的意思是说,像L<A>这样的每个应用程序都应该有* 。 对样的构造函数在您的示例中是否特殊,例如, (* => *) => * => *可以吗?


定义类型构造函数的结构方式

~<A, B> { 
    a : A,
    b : B
}
inferface TyCon<A, B> { 
    a : A,
    b : B
}

除了第一个匿名以外,这些示例是否有所不同?


子类型关系

~A~B不引用类型,因此它们具有子类型关系有意义吗? 您实际上何时需要检查一个构造函数是否是另一个构造函数的“子类型”? 是否可以等到构造函数被应用并检查结果类型后才可以?

@ jack-williams感谢您的反馈!

构造不完整类型是什么意思? 您是说每个像L<A>类的应用程序都应该有*号。 对样的构造函数在您的示例中是否特殊,例如, (* => *) => * => *可以吗?

对,就是这样。 每个应用程序,例如L<A>都应具有种类* 。 我不确定该如何出售。


除了第一个匿名以外,这些示例是否有所不同?

第一个是类型表达式,第二个是声明。 它们在大多数方面是相同的,就像这些类型在大多数方面是相同的一样:

{
     a : number;
     b : string;
}

interface Blah {
    a : number;
    b : string;
}

语法有多种动机:

  1. 就像TypeScript中的所有其他内容一样,它允许在结构上和匿名地指定类型构造函数。
  2. 类型表达式(如上述类型的匿名对象)可以在某些不能使用声明语句的上下文中使用,例如在函数的类型签名中。 这使他们可以捕获本地标识符并表达其他方式无法表达的内容。

~A~B不引用类型,因此它们具有子类型关系有意义吗? 您实际上何时需要检查一个构造函数是否是另一个构造函数的“子类型”? 是否可以等到构造函数被应用并检查结果类型后才可以?

类型构造器可能会也可能不会被视为类型。 我建议将它们视为类型,只是不具有值的不完整类型,并且不能出现在需要值类型的任何上下文中。 这与Scala在本文档中采用的相同哲学

所谓子类型关系,本质上是指某种可用来约束类型构造函数的“一致性”关系。 例如,如果我想编写一个可处理各种类型的Promise的函数,例如Promise<T>Bluebird<T>等等,我需要具有使用约束TC参数的功能。界面PromiseLike<T>以某种方式。

这种类型的关系的自然词是子类型关系。

让我们来看一个例子。 假设我们已经计算出类型构造函数之间的子类型关系,我可以编写如下函数:

function mapPromise<~P extends ~PromiseLike, A, B>(promise : P<A>, func : (x : A) => B) : P<B>;

约束~P extends ~PromiseLike应该保证这是一个仅在promise上起作用的函数。 该约束还将保证在函数体内,已知promise实现PromiseLike<A> ,依此类推。 毕竟,TypeScript在函数主体中识别的成员正是可以通过约束证明存在的成员。

以相同的方式Promise<T> extends PromiseLike<T> ,因为它们在结构上兼容并且可以彼此替换, ~Promise extends ~PromiseLike因为它们在结构上兼容,因此可以彼此替换。


要强调子类型问题,请再次考虑:

interface MyPromise<T> extends Promise<T[]> {}

我们可以像提取~MyPromise一样提取~Promise吗? 我们如何捕捉它们之间的关系?

我前面谈到的映射是,给定的参数映射~MyPromise ,将产生的参数~Promise使通过构造的该类型~MyPromise是的一个亚型一个由~Promise构造。

在这种情况下,映射如下所示:

T => T[]

@格雷格罗斯

在这种情况下, ~MySpecialPromise不是~PromiseLike的子类型,因为它们具有不同的种类。

在Haskell中,可以通过允许部分应用类型并定义类型来解决此类问题,以便最终的type参数与您要实现的任何接口的type参数一致。

在您的示例中, MySpecialPromise将被定义为MySpecialPromise<TSpecial, TPromiseVal> ,而~MySpecialPromise<SpecialType>将与~Promise具有相同的种类。

@格雷格罗斯

所谓子类型关系,本质上是指某种可用来约束类型构造函数的“一致性”关系。 例如,如果我想编写一个可处理各种类型的Promise的函数,例如Promise蓝鸟等等,我需要能够通过接口PromiseLike约束TC参数某种程度上来说。
function mapPromise<~P extends ~PromiseLike, A, B>(promise : P<A>, func : (x : A) => B) : P<B> ;

我认为,当要对该函数进行类型检查时,您将尝试统一BlueBird<T>PromiseLike<T> T ,这些只是具体类型,属于子类型化。 我不明白为什么您需要为构造函数~BlueBird~PromiseLike建立特殊关系。

我猜它会用在这样的事情上吗?


let x: <P extends ~PromiseLike>(input : P<A>, func : (x : A) => B) : P<B>;
let y: <P extends ~BlueBird>(input : P<A>, func : (x : A) => B) : P<B>;
x = y;

在这里,您可能要检查y的约束是否暗示x的约束,但是TypeScript是否没有机器可以检查BlueBird<T>扩展PromiseLike<T>可以使用的

@ jack-williams取决于您如何指定以下约束:

〜P是类型构造函数,对于所有AP<A>PromiseLike<A>的子类型。

您将使用哪种语法? 您将使用哪种概念? 您可以这样写:

function mapPromise<~P, A, B where P<A> extends PromiseLike<A>>

但是这种语法有局限性。 例如,您根本无法表达此类,因为我们不能在声明该类型的位置构造P<A>来约束它:

class PromiseCreator<~P extends ~PromiseLike> {
    create<A>() : P<A>;
}

但是我想您可以为此使用存在类型

//Here A is not a captured type parameter
//It's an existential type we introduce to constrain ~P
class PromiseCreator<~P with some A where P<A> extends PromiseLike<A>> {
    create<A>() : P<A>;
}

然后,您可以要求所有类型构造函数在函数或类型的签名中通过其构造类型进行约束,可以选择使用存在性类型。

对于存在性类型,这将具有与映射的子类型关系相同的表达能力。

但是,这将有多个问题:

  1. ((* => *) => *) => *类的类型指定类型构造函数将需要引入许多存在性类型,其中一些必须是高阶的。 它们都必须使用函数或类的签名。
  2. 我不完全确定,找到存在的类型比查找映射要容易。
  3. 我认为它不如子类型关系优雅。
  4. 可能会引入您必须处理的另一种类型的类型。

@格雷格罗斯

您将使用哪种语法? 您将使用哪种概念?

_Personally_我不会使用任何特殊语法,而只会使用:

function mapPromise<P extends PromiseLike, A, B>(p: P<A>, f: (x: A) => B): P<B>

class PromiseCreator<P extends PromiseLike> {
    create<A>() : P<A>;
}

但这只是我的看法,因为我将number视为空二进制构造函数:因此,无需区别对待。

我对构造函数的子类型的看法是使其尽可能简单。 它们应该具有相同的稀疏性,并且参数应该彼此类似,并且像Scala论文一样考虑到协方差和协方差。

部分应用程序可以解决它们具有不同Arity的情况(我不介意类型构造函数的auto-curryng,因此您只需编写MySpecialPromise<SpecialType> )。

在示例interface MyPromise<T> extends Promise<T[]> {}我必须老实说,我不认为处理这种情况值得付出复杂性-如果没有它,我认为这将是一个足够有用的功能。

处理这种情况等效于(我认为),说: ~MyPromise extends ~(Promise . [])其中[]是列表构造函数,而.是构造函数组成。 这似乎使事情变得越来越困难,因为现在仅检查构造函数的结构还不够,但是您也必须考虑组成!

@ jack-williams这不适用于默认类型参数。 如果我写P extends Foo ,其中Foo具有默认类型参数,即type Foo<T = {}> = ... ,那么P什么?

我只想说我赞成使用高阶类型(我在真实的TypeScript项目中遇到过有用的情况)。

但是我认为他们应该支持钻营。 我爱Haskell,但这与TypeScript不兼容。

高阶类型非常有用,即使没有currying或部分应用程序,但如果需要部分应用程序,我希望看到它的显式语法。 像这样:

Foo<number, _>  // equivalent to `type Foo1<A> = Foo<number, A>`

@ cameron-martin

编辑:对不起,我认为我的评论不是很清楚。 P具有其自己的种类,我的意思是它的使用对其施加了种类。 说约束总是假定为最高种类,因此假定Foo~Foo 。 仅当我们强制P是较低的种类时,我们才会检查Foo是否具有默认参数。 我对此的担心是一种善意的推断,但在那种情况下, ~无济于事,我认为我们需要完整的注释。

P有它自己的种类,不是吗? 问题不是我们会将Foo视为~Foo还是Foo<{}> :我认为这将由P来驱动。因此,如果P是类型,我们强制使用默认参数,如果P是构造函数* => * ,则我们将Foo视为相同。

@Pauan同意您的建议。

正如我前面提到的,@ jack-williams我已经考虑了子类型化的概念:

我的第一个想法是。 对于~A~B的子类型:

  1. (a)它们必须具有相同的种类(在友善而非约束方面)。
  2. (b)对于每一个法律的参数T₁, T₂, ...~AA<T₁, T₂, ...>必须的子类型B<T₁, T₂, ...>

问题是,如果我们使事情尽可能简单,那么最终将导致一个自相矛盾且不适合该语言的子类型关系。

如果MyPromise<T> extends Promise<T[]>意味着MyPromise<T>必须在Promise<T[]>可用的任何地方都可用,但是情况不再如此。

如果您使用as来将a : MyPromise<T>Promise<T[]> ,您将被转换,但这反而会使a分配性更高。

遵循现有子类型关系的现有通用约束也可以用于实现类似效果并引起奇怪的行为:

function id1<A, ~P extends ~PromiseLike>(p : P<A>) : P<A>;

function id2<A, P extends Promise<A[]>>(p : P) : P {
    //ERROR - P does not extend PromiseLike<A>
    return id1(p);
}

打字也至少会部分成为名义上的副作用。 这些类型在当前相同的地方会突然不同:

type GenericNumber<T> = number;

type RegularNumber = number;

我什至不知道它将对具有类型参数,纯结构类型,具有成员理解的类型等复杂的联合/交集类型产生什么影响。

我个人的感觉是:类型构造函数上的子类型关系需要尊重现有的,而不是反对它。 可悲的是,这要求事情变得更加复杂。


对类型构造函数使用某种特殊符号的最大原因是99%的开发人员不知道什么是类型构造函数,也不希望被有关它们的错误消息所轰击。

这与Haskell完全不同,Haskell在法律上要求每个开发人员都必须学习类别理论的高级课程。

第二个原因是,在某些情况下(如上述默认参数情况),语法要么是模棱两可的,要么根本不可能抽象出特定类型的构造函数。

编辑:对不起@GregRos我没有看到您后面的评论!

类型构造函数上的子类型关系需要尊重现有的,而不是反对它。

如果可以实现,那我同意。 我只是不了解所有细节,这很容易。

第二个原因是,在某些情况下(如上述默认参数情况),语法要么是模棱两可的,要么根本不可能抽象出特定类型的构造函数。

我不确定我是否同意,如果您始终假设约束的最高种类,直到您需要降低约束,那将是模棱两可的。 这不是一个断言,如果还有其他示例说明,那么就足够了。


问题是,如果我们使事情尽可能简单,那么最终将导致一个自相矛盾且不适合该语言的子类型关系。

可能是对的,我想我只是担心替代方案是否可能真正实现。 就其价值而言,如果更复杂的解决方案有效,那就太好了!

一般而言,具有子类型的更一般的概念来显示映射功能的存在似乎很难实现。 我的以下示例是否正确解释了您的规则?

(b)对于〜A的每个合法参数化T 1,T 2,...,存在〜B的参数化S 1,S 2,...,使得A

给定F(A,B)=(number,B)的映射,在以下情况下X是Y的子类型。

type X = ~<A,B> = {x : B};
type Y = ~<A,B> = A extends number ? {x: B} : never;

但是X<string,number>不会是Y<string,number>的子类型。

我想我不清楚映射的_existence_是否足够。 如果我们将〜A和〜B当作函数,并且我们想证明〜B近似于〜A,或者〜A是〜B的子类型,则表明存在某些函数〜C,使得〜A是a我认为(〜B。〜C)的子类型还不够(C是映射器)。 _all_映射必须如此。

function id1<A, ~P extends ~PromiseLike>(p : P<A>) : P<A>;

function id2<A, P extends Promise<A[]>>(p : P) : P {
    //ERROR - P does not extend PromiseLike<A>
    return id1(p);
}

我不太喜欢这个例子,这里的错误应该不会发生吗? 我对这些的理解是id1应该有一个由函数P构造的输入,该输入将为所有_inputs_提供一个PromiseLike 。 而id2所讨论的值必须是将Promise应用于A []的子类型。 我不确定是否可以从id2类型恢复id1必要信息。 我想我可能会误解你的意思。

这些类型会突然不同,目前它们是相同的

再说一次,恐怕我可能会遗漏您的观点,但它不知道它们的相同之处。 我不能代替RegularNumberGenericNumber的类型,我将不得不考虑到后者的参数。

我想我不清楚映射是否足够。 如果我们将〜A和〜B当作函数,并且我们想证明〜B近似于〜A,或者〜A是〜B的子类型,则表明存在某些函数〜C,使得〜A是a我认为(〜B。〜C)的子类型还不够(C是映射器)。 所有映射都必须如此。

是的,您是对的,您提供的反例也是。 我发现了其他反例。 根本不起作用。

我已经重读了这个主题以及您的许多回复。 我认为您在很多事情上都是对的,而且我一直以错误的方式看待问题。 我会明白我的意思。

我不确定我是否同意,如果您始终假设约束的最高种类,直到您需要降低约束,那将是模棱两可的。 这不是一个断言,如果还有其他示例说明,那么就足够了。

它要么模棱两可,要么变得无法引用。 就像上面的示例一样, Foo的类型构造函数变得无法引用,因为它被类型本身隐藏了。 如果您编写~Foo或与此相关的Foo<*>~<A>Foo<A>或其他与其他内容不冲突的内容,则不会遇到此类问题。

是的,您可以通过定义别名来解决它,尽管它不是很漂亮:

type Foo2<T> = Foo<T>

就像我说的,我认为这不是最重要的问题。

我不太喜欢这个例子,这里的错误应该不会发生吗? 我对这些的理解是,id1应该具有由函数P构造的输入,该输入为所有输入提供PromiseLike。 而id2讨论的值必须是将Promise应用于A []的子类型。 我不确定是否可以从id2的类型恢复id1所需的信息。 我想我可能会误解你的意思。

是的,这是正确的阅读。 但是,如果P extends Promise<A[]>应该可以分配给任何接受Promise<A[]> ,例如id1 。 这就是现在的方式,以及子类型化的含义。

我真的认为不能再避免了。

再说一次,恐怕我可能会遗漏您的观点,但它不知道它们的相同之处。 我不能用类型中的GenericNumber替换RegularNumber,我必须给后者一个参数。

我的意思是:类型GenericNumber<T> ,对于所有T ,类型RegularNumber是相同且可互换的。 没有上下文可以输入检查,而另一种则不会。 至少现在。

我们一直在谈论将使他们与众不同。 由于GenericNumber<T>是来自TC的,因此在RegularNumber不可能的地方很有用。 因此它将不再可互换。

我已经考虑过了,我想这可能是不可避免的,不一定是坏事。 只是一种新的,不同的行为。

一种考虑的方法是,类型参数成为类型“结构”的一部分。

我确实认为TC会导致更多不同的行为。

新方向

首先,我认为您是对的,因为正确的子类型关系是没有映射的关系:

我的第一个想法是。 对于~A~B的子类型:

  1. (a)它们必须具有相同的种类(在友善而非约束方面)。
  2. (b)对于~A每个合法参数化T₁, T₂, ...A<T₁, T₂, ...>必须是B<T₁, T₂, ...>的子类型。

映射的东西...说实话,这很愚蠢。 我认为不再有任何方法可以统一MyPromise<T> extends Promise<T[]>~Promise 。 我很想知道是否有人有其他想法。

我也很想知道是否有一个我连这个规则都不起作用的例子。

如果我们确实同意应该使用子类型关系来表示类型构造函数约束,这种关系看起来确实工作得很好,那么我们可以转向其他事情。

关于语法

看来,我们在这个问题上并没有真正达成共识。 我强烈希望使用类似于~Promise的前缀语法。 从概念上讲, ~可以看作是“引用TC of”运算符或其他内容。

我认为我已经给出了几个优于替代方法的原因:

  1. 完全不含糊。

    1. 涉及此语法的错误也很明确。 如果有人忘记对类型进行参数化,那么当他们不知道它们是什么时,他们将不会得到有关类型构造函数的错误。

    2. 副作用是,不必更改现有的错误文本和逻辑。 如果有人写Promise则错误消息将与现在完全相同。 谈论TC无需更改。

  2. 很好地扩展了结构语法。
  3. 我相信很容易解析。 任何出现在期望类型的~\w都将被视为指示对TC的引用。
  4. 易于输入。

我希望其他人可以发表意见。

关于联合,相交和默认类型参数

重载/混合形式的* & (* => *)* | (* => *)等合法吗? 他们有有趣的用途吗?

我认为这是个坏主意,很难推理。 我也不确定您需要哪种类型的注释来消除* | (* => *)歧义,以便您可以从中构造类型。

可以说这种类型现在存在的一种方式是具有默认类型参数的类型:

type Example<A = number> = {}

可以说此类型为* & (* => *)类型,因为它可以接受类型参数来构造类型,但是不必这样做。

我认为默认类型参数应该是一种简写形式,而不是描述类型的一种方式。 因此,我认为在确定类型的类型时应该忽略默认类型参数。

但是,谈论诸如~Promise | ~Array类的类型可能很有意义。 它们具有相同的种类,因此它们不是不兼容的。 我认为这应该得到支持。

必须处理的事情

必须处理相关情况,例如以下情况:

type Example = (<~P extends ~Promise>() => P<number>) | (<~M extends ~Map>() => Map<string, number>);

但这并没有真正涉及那种(* => *) | (*, *) => * ,而是有所不同

建造其他TC?

就像我之前提到的,我认为让TC构造其他TC(例如* => (* => *)不是一个好主意。 它们是支持currying等语言的规范,但不是TypeScript中的规范。

我完全看不到使用~语法和子类型关系定义此类的方法,因此不需要任何特殊规则来禁止它们。 它将需要特殊的规则才能使它们起作用。

我猜您可以这样定义它们的结构:

~<A>~<B>{a : A, b : B}

那几乎是我思考的唯一方法。

与更高级别的功能进行交互?

与带有类型参数的函数类型之间存在自然而复杂的交互:

type Example<T> = <~P extends ~Promise>(p : P<T>) : P<T>;

是否应该以某种方式停止这种互动? 我可以看到像这样的类型变得非常复杂。

通常,是否有不应该出现TC参数的地方?

我的结构语法是个好主意吗?

我不认为应该立即实现它,但是我仍然认为结构语法是一个好主意。 它可以让您:

  1. 在约束中使用像其他类型参数一样的本地标识符。
  2. 以非常明确和灵活的方式部分应用类型构造函数: ~<A>Map<string, A>~<A, B>Map<B, A> ,依此类推。
  3. 类型的每个其他方面都具有结构语法,因此TC也应具有这种语法。

话虽如此,TC完全可以在没有这种情况的情况下工作,而第一个PR可能不会涉及它们。

与条件类型的交互

功能如何与条件类型一起使用? 您应该能够做到吗?

type Example<~P extends ~PromiseLike> = ~P extends ~Promise ? 0 : 1

我不确定自己。 尚未完全消化条件类型。

过载分辨率

我觉得这将很难做到。 这实际上非常重要,因为不同形式的重载解决方案最终将构造不同的类型。

就是说,我现在无法提出任何好的例子。

您知道,如果使用良好定义的中间语言将TypeScript描述为起点,那么很多事情就没有意义了。 例如: System F <:或声音依赖类型系统之一,例如Simplified Dependent ML

如果这个问题早日解决,我会感到很惊讶
https://github.com/Microsoft/TypeScript/issues/14833

我觉得#17961可能可以间接解决此问题。 有关更多详细信息,请参见此要点

请注意, BifunctorProfunctor类型在约束级别上有点复杂-如果要使用明显的通用类型,则比infer T要简单得多。 this用作“返回”类型(纯粹是类型级别),那就太好了,这会使我的大多数接口更易于定义。

(我不是大量的TS用户,所以我可能会犯错。 @ tycho01您能否看一下,看看我是否搞砸了类型混乱的任何地方?我问的原因是因为您是落后的那个人以上PR,我已经看过您的其他一些实验和实用程序。)

@isiahmeadows @ tycho01哇...

你是对的。 如果我理解正确,它的结果几乎相同。

有一些区别。 但是在功能上,它们几乎是相同的,我认为这些差异可以解决。

无法推断正确的类型函数

function example<~P extends ~PromiseLike>(p : P<number>) : P<string>;

在这里,您可以从p推断~Promise~Bluebird p 。 但是,如果您这样做:

function example<F extends <T>(t: T) => PromiseLike<T>>(p : F(number)) : F(string)

我对此表示怀疑:

example(null as Promise<number>)

无法推断F的含义是:

<T>(t : T) => Promise<T>

因为此功能在任何方面都不被认为是特殊的。 相对于TC,某些类型本质上具有“隐式”类型级别的功能:它们的TC。

无法轻松引用现有的TC

您不能像我的提案中那样执行~Promise 。 您必须使用结构格式直接对类型进行编码:

type PromiseTC = <T>() => Promise<T>

是的,这是一个问题。 这更多是类型推断问题,您需要具有从已知参数类型推断通用函数本身的能力(与通常发生的情况相反)。 它可以通用的方式解决,适合大多数情况,但需要一个新的特殊情况,这是不平凡的。

它可以部分地通过NoInfer<T>战略用途来解决,但我不确定100%会需要怎么做,甚至可以解决常见问题。

@格雷格罗斯

我不太赞成任何语法,它只是我的偏爱, ~有很多优点。 我认为要考虑的主要问题是是否需要使用显式种类注释的语法,因为推断并不总是可能的。


映射的东西...说实话,这很愚蠢。 我认为没有任何方法可以统一MyPromise扩展承诺

我认为映射事物可能仍然是一个有用的概念,但在上述情况下,我认为我们永远都不应尝试将~MyPromise~Promise统一,这需要进行统一是~MyPromise~<T>Promise<T[]> ,我们也可以写成~(Promise . []) 。 我认为缺少的是映射需要成为子类型关系的一部分:它与Promise一样是构造函数的一部分。 在该示例中,映射只是列表构造函数。

interface A<T> {
    x: T;
} 

interface B<T> {
    x: T[];
}

~<T>B<T>延长~<T>A<T[]>吗? 是。 ~<T>B<T>延长~<T>A<T>吗? 不会。但是最终,这是两个不相关的问题。

如果我们确实同意应该使用子类型关系来表示类型构造函数约束,这种关系看起来确实工作得很好,那么我们可以转向其他事情。

是的,我认为这似乎是描述事物的好方法。


无法推断正确的类型函数

function example<~P extends ~PromiseLike>(p : P<number>) : P<string>;
在这里,您可以从p推断~Promise~Bluebird

这不是断言,而是一个悬而未决的问题,因为我不确定类型检查的工作原理。 我以为使用上面的接口A作为示例,类型A<number>{x: number}是无法区分的,因此我不确定是否可以推断出构造函数从构造函数的应用程序返回的类型。 有可能从P<number>恢复P P<number>吗? 我敢肯定,为了支持这一点,事情可能会改变,我只是想知道它现在在做什么。

从#17961开始交叉响应,但是不幸的是,我不确定如何使@isiahmeadows的方法起作用。 我担心对类型调用的向后推论是不平凡的。

因此,基于输入Promise<number>Bluebird<number>我们希望能够推断出这些类型的未应用版本,以便我们可以使用string重新应用它们。 不过,这听起来很难。

即使输入类型是这种形式,而不是某种结构上的等价形式(我们是一种结构类型的语言,对吗?),如果例如Bluebird具有两个参数类型,则这种推理也会变得模糊不清,此时我们的<string>类型的参数应用程序可能不再有意义。
我不确定那里是否有好的解决方案。 (免责声明:我在这里的讨论有些落后。)

@ tycho01如果人们显式实例化T所有这些问题会消失吗?

我认为这是合理的,因为我怀疑无论如何都无法解决推断问题。

@ jack-williams:到目前为止还没有#17961,但是我认为将其用于调度可能会有所帮助:

let arr = [1, 2, 3];
let inc = (n: number) => n + 1;
let c = arr.map(inc); // number[]
let map = <Functor extends { map: Function }, Fn extends Function>(x: Functor, f: Fn) => x['map'](f); // any on 2.7 :(
let e = map(arr, inc);

@ tycho01是的,我意识到我的建议很糟糕,因为T没有在方法调用中实例化。

可以进行以下工作吗?

interface TyCon<A> {
    C: <A>(x: A) => TyCon<A>
}

interface Functor<A> extends TyCon<A> {
    C: <A>(x: A) => Functor<A>;
    fmap<B>(this: this["C"](A), f: (x: A) => B): this["C"](B);
}

interface Option<A> extends Functor<A> {
    C: <A>(x: A) => Option<A>;
}

@ jack-williams我想这个问题应该是它在行为上与fp-ts的ADT实现进行比较的方式,但这看起来可以工作,是的。 可能也没有TyCon

@ jack-williams @isiahmeadows

我尝试了try flow的想法,因为它已经有$Call可用。 对我而言,它似乎变得毫无反应...

interface Functor<A> {
    C: <A>(x: A) => Functor<A>;
    fmap<B>(f: (x: A) => B): $Call<$ElementType<this, "C">, B>;
}
// this: $Call<$ElementType<this, "C">, A>, 
// ^ flow doesn't seem to do `this` params

interface Option<A> extends Functor<A> {
    C: <A>(x: A) => Option<A>;
}

let o: Option<string>;
let f: (s: string) => number;
let b = o.fmap(f);

@ tycho01我想你不能只从this的流量中获取$ElementType属性

@ tycho01您实际上也无法在打字稿中使用它
游乐场: https :

@goodmind :hm,在将fmapFunctor复制到fmap之后,看起来像是推断Maybe<number>而不是Functor<number> Maybe
通过类型调用,我猜想这将改进为仅在其中放置类型,而不需要类型的运行时实现。
现在,函子已经需要他们自己的fmap实现。 不过,这对于派生的方法来说很糟糕。
回到原点。 :/

我打算尽快发布一个Alpha版本,但是您可以跟我一起编写该问题中的示例,以便对此有所了解。

这个特殊的问题要花很长时间才能完全解决,但是我要查找的内容很简单,但是由于缺少参数化的泛型类型而无法键入的实际代码示例。 我想我可以键入大多数(前提是它们不依赖抽象的提升类型构造函数)。 可以使用代码随意在上述存储库中打开问题,如果可以的话,我会为您键入(或您也可以在此处发布)。

抬头,我已经开始尝试在#23809上实现此目标。 它仍然很不完整,但是请检查一下是否有兴趣。

我向你们保证,这是一个简单的例子。 这使用了我从编写库中学到的一些技巧。

type unknown = {} | null | undefined;

// Functor
interface StaticFunctor<G> {
    map<F extends Generic<G>, U>(
        transform: (a: Parameters<F>[0]) => U,
        mappable: F
    ): Generic<F, [U]>;
}

// Examples
const arrayFunctor: StaticFunctor<any[]> = {
    map: <A, B>(fn: (a: A) => B, fa: A[]): B[] => {
        return fa.map(fn);
    }
};
const objectFunctor: StaticFunctor<object> = {
    map: <A, B>(fn: (a: A) => B, fa: A): B => {
        return fn(fa);
    }
};
const nullableFunctor: StaticFunctor<object | null | undefined> = {
    map: <A, B>(
        fn: (a: A) => B,
        fa: A | null | undefined
    ): B | null | undefined => {
        return fa != undefined ? fn(fa) : fa;
    }
};

const doubler = (x: number) => x * 2;

const xs = arrayFunctor.map(doubler, [4, 2]); // xs: number[]
const x = objectFunctor.map(doubler, 42); // x: number
const xNull = nullableFunctor.map(doubler, null); // xNull: null
const xSome = nullableFunctor.map(doubler, 4 as number | undefined); // xSome: number | undefined

const functor: StaticFunctor<unknown | any[]> = {
    map(fn, fa) {
        return Array.isArray(fa)
            ? arrayFunctor.map(fn, fa)
            : fa != undefined
                ? objectFunctor.map(fn, fa)
                : nullableFunctor.map(fn, fa);
    }
};

const ys = functor.map(doubler, [4, 2]); // ys: number[]
const y = functor.map(doubler, 42); // y: number
const yNull = functor.map(doubler, null); // yNull: null
const ySome = functor.map(doubler, 42 as number | undefined); // ySome: number | undefined

// Plumbing
interface TypeProps<T = {}, Params extends ArrayLike<any> = never> {
    array: {
        infer: T extends Array<infer A> ? [A] : never;
        construct: Params[0][];
    };
    null: {
        infer: null extends T ? [never] : never;
        construct: null;
    };
    undefined: {
        infer: undefined extends T ? [never] : never;
        construct: undefined;
    };
    unfound: {
        infer: [NonNullable<T>];
        construct: Params[0];
    };
}

type Match<T> = T extends infer U
    ? ({} extends U ? any
        : TypeProps<U>[Exclude<keyof TypeProps, "unfound">]["infer"]) extends never
    ? "unfound"
    : {
        [Key in Exclude<keyof TypeProps, "unfound">]:
        TypeProps<T>[Key]["infer"] extends never
        ? never : Key
    }[Exclude<keyof TypeProps, "unfound">] : never;


type Parameters<T> = TypeProps<T>[Match<T>]["infer"];

type Generic<
    T,
    Params extends ArrayLike<any> = ArrayLike<any>,
    > = TypeProps<T, Params>[Match<T>]["construct"];

我更新并简化了示例,这也是一个游乐场链接:
操场

我为上述内容添加了NPM库,因此您可以更轻松地使用它。 目前尚处于测试阶段,直到获得适当的测试为止,但应该会帮助尝试编写HKT的人们。

Github链接

我一直在使用一种简单的方法来模拟HKT,方法是使用条件类型替换饱和类型内的虚拟类型变量:

declare const index: unique symbol;

// A type for representing type variables
type _<N extends number = 0> = { [index]: N };

// Type application (substitutes type variables with types)
type $<T, S, N extends number = 0> =
  T extends _<N> ? S :
  T extends undefined | null | boolean | string | number ? T :
  T extends Array<infer A> ? $Array<A, S, N> :
  T extends (x: infer I) => infer O ? (x: $<I, S, N>) => $<O, S, N> :
  T extends object ? { [K in keyof T]: $<T[K], S, N> } :
  T;

interface $Array<T, S, N extends number> extends Array<$<T, S, N>> {}

// Let's declare some familiar type classes...

interface Functor<F> {
  map: <A, B>(fa: $<F, A>, f: (a: A) => B) => $<F, B>;
}

interface Monad<M> {
  pure: <A>(a: A) => $<M, A>;
  bind: <A, B>(ma: $<M, A>, f: (a: A) => $<M, B>) => $<M, B>;
}

interface MonadLib<M> extends Monad<M>, Functor<M> {
  join: <A>(mma: $<M, $<M, A>>) => $<M, A>;
  // sequence, etc...
}

const Monad = <M>({ pure, bind }: Monad<M>): MonadLib<M> => ({
  pure,
  bind,
  map: (ma, f) => bind(ma, a => pure(f(a))),
  join: mma => bind(mma, ma => ma),
});

// ... and an instance

type Maybe<A> = { tag: 'none' } | { tag: 'some'; value: A };
const none: Maybe<never> = { tag: 'none' };
const some = <A>(value: A): Maybe<A> => ({ tag: 'some', value });

const { map, join } = Monad<Maybe<_>>({
  pure: some,
  bind: (ma, f) => ma.tag === 'some' ? f(ma.value) : ma,
});

// Not sure why the `<number>` annotation is required here...
const result = map(join<number>(some(some(42))), n => n + 1);
expect(result).toEqual(some(43));

项目在这里: https :

欢迎反馈!

@pelotom我喜欢您的方法语法的简洁性。 在该主题中还没有提到其他两种方法,这些方法可能会激发如何产生当前和将来的解决方案的创造力。 两者都是此问题的面向对象的解决方案。

  1. Bertrand Meyer在1988年的著作《面向对象的软件构造》中描述了一种模拟泛型类型的方法。

这些示例在Eiffel中,但是对TypeScript的粗略翻译如下所示:

https://gist.github.com/mlhaufe/089004abd14ad8e7171e2a122198637f

您会注意到,由于需要中间类表示形式,它们可能会变得很沉重,但是使用类工厂形式或使用TypeScript Mixin方法,可以大大减少这种情况。

#17588可能有一些适用性

  1. 在模拟对象代数(和抽象工厂)时使用第二种方法:

C<T>App<t,T> ,其中T是类,而t是与C相关的唯一标记

interface App<C,T> {}

样品:

interface IApp<C,T> {}

interface IList<C> {
    Nil<T>(): IApp<C,T>
    Cons<T>(head: T, tail: IList<C>): IApp<C,T>
}

// defining data
abstract class List<T> implements IApp<typeof List, T> {
    // type-safe down-cast
    static prj<U>(app: IApp<typeof List, U>): List<U> { return app as List<U> }
}
class Nil<T> extends List<T> { }
class Cons<T> extends List<T> {
    constructor(readonly head: T, readonly tail: List<T>) {
        super()
    }
}

// The abstract factory where the HKT is needed
class ListFactory<T> implements IList<typeof List> {
    Nil<T>(): IApp<typeof List, T> { return new Nil() }
    Cons<T>(head: T, tail: IApp<typeof List, T>): IApp<typeof List, T> {
        return new Cons(head, tail)
    }
}

您可以在下面的第3.5节“模拟类型构造函数多态”下的文章中查看更多详细信息和理由:

https://blog.acolyer.org/2015/08/13/streams-a-la-carte-extensible-pipelines-with-object-algebras/

@metaweta ,您可以将此问题重命名为Higher kinded types in TypeScript以便从Google搜索获得更好的可见性吗?

也许我们明智和仁慈的存储库维护者(例如, @RyanCavanaugh@DanielRosenwasser )可以编辑标题,如果认为这样的更改值得他们干预?

我很好奇,这已经意味着从社区转移到积压。 这是核心团队现在正在认真考虑的问题,还是仅仅意味着该团队已确定这不是一个好的社区候选人?

发现它:显然不赞成使用“社区”里程碑,而推荐使用“积压” ,因此,此问题可能已以实物形式移植。

不是TS成员,而是决定单击链接重新里程碑的人。

+1

这是我刚刚尝试构建的,对于高类型的类型来说,这似乎是一个非常实际的案例。

我想为可以同步或异步运行的数据库创建一个抽象。 我不想使用回调和修改,而是想使用泛型。 这是我想做的:

type Identity<T> = T

interface DatabaseStorage<Wrap<T> extends Promise<T> | Identity<T>> {
    get(key: string): Wrap<any>
    set(key: string, value: any): Wrap<void>
}

这将非常强大!

@ccorcos那就是MTL风格。 您可以在https://github.com/gcanti/fp-ts/blob/master/tutorials/mtl.ts中查看有关fp-ts的纯函数示例。

@mlegenhausen,对不起,但是我很难理解那个例子。

每当我深入研究fp-ts ,我都会担心事情变得如此复杂以至于它们会变得脆弱。 @pelotom的示例看起来更容易理解...

任何原因都没有被TypeScript采纳?

@ccorcos恕我直言,即使我从fp-ts推荐了示例,我也不推荐MTL /无标签样式。 您为需要手动管理的每个有效monad添加了额外的抽象层,这是因为typescript无法检测到要使用的monad,这使事情变得复杂。 我从fp-ts社区中看到的是使用一个异步monad(我会推荐TaskEither )并坚持使用。 即使在测试MTL的好处上,也不值得在非测试代码中麻烦。 基于fp-ts hyper-ts是最近放弃对MTL的支持的一个例子。

有趣的是... hyper-ts看起来真的很酷...

我想出了一种基于F界多态性的轻量级,种类繁多的类型编码: https

这种方法的好处是您不需要查找表(单个条件类型或具有字符串键的对象)即可将类型构造函数与类型相关联。 该技术还可以用于对类型级别的泛型函数的应用进行编码(请考虑ReturnType<<T>(value: T) => Array<T>> )。

它仍然是概念证明,因此非常感谢您对该方法的可行性提出反馈!

我来看一下@strax ,它看起来真的很酷!

同时,这是我们现在可以做的一个愚蠢的例子:

type Test1 = λ<Not, [True]>;        // False
type Test2 = λ<And, [True, False]>; // False
type Test3 = λ<And, [True, True]>;  // True

// Boolean

interface True extends Func {
    expression: Var<this, 0>;
}

interface False extends Func {
    expression: Var<this, 1>;
}

interface Not extends Func {
    expression: λ<Var<this, 0>, [False, True]>
}

interface And extends Func {
    expression: λ<Var<this, 0>, [Var<this, 1>, Var<this, 0>]>
}

// Plumbing

type Func = {
    variables: Func[];
    expression: unknown;
}

type Var<F extends Func, X extends number> = F["variables"][X];

type λ<Exp extends Func, Vars extends unknown[]> = (Exp & {
    variables: Vars;
})["expression"];

我想添加De Bruijn索引,因为那将意味着我们不再需要接口,但是我认为它需要一些元组数学,并且我正试图避免这种情况。

提案

高阶,Lamda,参考类型

传递类型作为参考

简而言之,引用类型高阶类型将允许将一个类型采用的参数推迟到以后使用,甚至在以后再推断类型参数(泛型)。 但是我们为什么要关心呢?

如果我们可以将类型作为引用传递,则意味着可以延迟TypeScript对类型的求值,直到我们决定这样做为止。 让我们看一个真实的例子:

用管道预览

假设您正在为pipe开发通用类型。 大部分工作是关于检查要传递的功能确实可传递的,否则我们会给用户带来错误。 为此,我们将使用映射类型来传递pipe(...)之类的函数类型:

type  PipeSync<Fns  extends  Function[], K  extends  keyof  Fns> = 
    K  extends  '0'
    // If it's the first function, we leave it unchanged
    ?  Fns[K]
    // For all the other functions, we link input<-output
    : (arg:  Return<Fns[Pos<Prev<IterationOf<K & string>>>]>) =>
        Return<Fns[Pos<IterationOf<K & string>>]>;

现在,我们只需要对具有映射类型的函数重复此操作:

type  Piper<Fns  extends  Function[]> = {
    [K  in  keyof  Fns]:  PipeSync<Fns, K>
}

请参阅完整的实现

现在我们可以将函数管道在一起,并且TypeScript可以向我们发出警告:

declare  function  pipe<Fns  extends  F.Function[]>(...args:  F.Piper<Fns>):  F.Pipe<Fns>

const  piped  =  pipe(
    (name:  string, age:  number) => ({name, age}),
    (info: {name:  string, age:  number}) =>  `Welcome, ${info.name}`,
    (message:  object) =>  false, // /!\ ERROR
)

有用! 我们有一个正确的错误:

类型'(message:object)=> boolean'的参数不能分配给类型'(arg:string)=> boolean'的参数。

问题

但有一个问题。 尽管这对于简单的操作非常有效,但是当您开始在传递给它的函数上使用泛型(模板)时,您会发现它完全失败了:

const  piped  =  pipe(
    (a:  string) =>  a,
    <B>(b:  B) =>  b, // any
    <C>(c:  C) =>  c, // any
)

type  piped  =  Piper<[
    (a:  string) =>  string,
    <B>(b:  B) =>  B,
    <C>(c:  C) =>  C,
]>
// [
//     (a:  string) =>  string,
//     (b:  string) =>  unknown,
//     (c:  unknown) => unknown
// ]

在这两种情况下,TypeScript都无法跟踪函数类型。
>这是高阶类型起作用的地方<

句法

type  PipeSync<Fns  extends  Function[], K  extends  keyof  Fns> = 
    K  extends  '0'
    // If it's the first function, we leave it unchanged
+   ?  *(Fns[K]) // this will preserve the generics
    // For all the other functions, we link input<-output
+   :  *( // <- Any type can be made a reference
+       <T>(arg:  T) => Return<*(Fns[Pos<IterationOf<K  &  string>>])>
+       // vvv It is now a reference, we can assign generics
+       )<Return<*(Fns[Pos<Prev<IterationOf<K  &  string>>>])>>
+       // ^^^ We also tell TS not to evaluate the previous return
+       // and this could be achieved by making it a reference too

简而言之,我们使用*手动和动态地推断出泛型。 实际上,使用*延迟对泛型的评估。 因此, *具有不同的行为,具体取决于上下文。 如果*的类型为:

  • 可以接收泛型:它接管其泛型/获取其引用
  • 本身就是通用名称:它将评估推迟到知道引用为止。 为此,从父节点开始构建参考树。 换句话说,引用可以嵌套。 这正是以上情况Return<*(Fns[Pos<Prev<IterationOf<K & string>>>])>是得到了分配给T 。 在这种情况下,我们可以说* “保护”立即评估。
  • 以上都不是:不执行任何操作,解析为相同类型
type  piped  =  Piper<[
    (a:  string) =>  string,
    <B>(b:  B) =>  B
    <C>(c:  C) =>  C
]>
// [
//     (a:  string) =>  string,
//     (b:  string) =>  string,
//     (c:  string) =>  string
// ]

因此,TypeScript仅应在提供泛型后才开始/继续评估,并在需要时阻止评估(不完整的泛型)。 目前,TS通过将泛型转换为unknown类型来进行一次评估。 有了这个建议,当某些事情无法解决时:

type  piped  =  Piper<[
    <A>(a:  A) =>  A, // ?
    <B>(b:  B) =>  B, // ?
    <C>(c:  C) =>  C, // ?
]>
// [
//     <A>(a:  A) =>  A,
//     (b:  A) =>  A,
//     (c:  A) =>  A
// ]

细节

*检索对类型的引用,从而可以对其泛型进行操作。 因此,将通配符放在类型前面会检索对它的引用:

*[type]

检索对类型的引用会自动启用泛型操作:

*[type]<T0, T1, T2...>

泛型仅在可能的情况下由目标类型使用/设置。 这样做:

*string<object, null> // Will resolve to `string`

但是TypeScript本身也可以检查它是否应该显示警告。 但在内部,TS不应对此采取任何措施。

我还认为使用*是个好主意,因为它可以象征某个东西的指针(例如在C / C ++语言中),并且不会被TypeScript借用。

高阶订单类型

既然我们已经了解了它以最基本的形式工作的原理,那么我想介绍一下核心概念: lambda types 。 能够具有匿名类型,这很不错,类似于JavaScript中的回调,lambda和引用

上面的示例显示了如何接管函数的泛型。 但是由于我们在谈论引用,因此任何类型都可以与*结合使用。 简而言之,类型引用是我们可以传递但尚未收到其泛型的类型:

type  A<T  extends  string> = {0:  T}
type  B<T  extends  string> = [T]
type  C<T  extends  number> = 42

// Here's our lamda
type  Referer<*Ref<T  extends  string>, T  extends  string> =  Ref<T>
// Notice that `T` & `T` are not in conflict
// Because they're bound to their own scopes

type  testA  =  Referer<A, 'hi'> // {0: 'hi'}
type  testB  =  Referer<B, 'hi'> // ['hi']
type  testC  =  Referer<C, 'hi'> // ERROR

更高种类的类型

interface Monad<*T<X extends any>> {
  map<A, B>(f: (a: A) => B): T<A> => T<B>;
  lift<A>(a: A): T<A>;
  join<A>(tta: T<T<A>>): T<A>;
}

搜索词

#order#类型#引用#高级#lambda #HKT

@ pirix-gh如果您只阅读了几则消息,则可以看到您所要求的很多东西已经或者已经被要求。

我确实读过它们,我认为我可以像其他人一样总结我的想法(对于一个多合一的解决方案),主要是关于语法的。

我编辑了以上建议,以更好地解释我们如何链接引用,并修复了像Pipe这样的类型与之配合使用的方式(关于它的逻辑,存在一些错误)。

任何更新?

还是没有更新? 我认为,此问题是TypeScript发挥其全部潜力的第一大障碍。 在很多情况下,我尝试正确键入我的库,但经过长时间的努力才放弃,意识到我再次遇到了这种限制。 它无处不在,甚至在看似非常简单的场景中也显示出来。 真的希望它将尽快得到解决。

interface Monad<T<X>> {
    map1<A, B>(f: (a: A) => B): (something: A) => B;

    map<A, B>(f: (a: A) => B): (something: T<A>) => T<B>;

    lift<A>(a: A): T<A>;
    join<A>(tta: T<T<A>>): T<A>;
}

type sn = (tmp: string) => number

function MONAD(m: Monad<Set>,f:sn) {
    var w = m.map1(f);    // (method) Monad<Set>.map1<string, number>(f: (a: string) => number): (something: string) => number
    var w2 = m.map(f);    // (method) Monad<Set>.map<string, number>(f: (a: string) => number): (something: Set<string>) => Set<number>
    var q = m.lift(1);    // (method) Monad<Set>.lift<number>(a: number): Set<number>
    var a = new Set<Set<number>>();
    var w = m.join(q);    // (method) Monad<Set>.join<unknown>(tta: Set<Set<unknown>>): Set<unknown>.  You could see that typeParameter infer does not work for now.
    var w1 = m.join<number>(q);    // (method) Monad<Set>.join<number>(tta: Set<Set<number>>): Set<number>
}

仍然需要完成许多工作,例如:修复quickinfo,推断typeParameter,添加错误消息,突出显示相同的typeConstructor .....
但是它开始起作用,这就是我现在可以得到的。
该示例界面来自@millsp https://github.com/microsoft/TypeScript/issues/1213#issuecomment -523245130,结论确实非常有用,非常感谢。

我希望社区可以提供更多类似的用例,以检查当前方法是否适用于大多数情况。

提供有关HKT /函数编程/ lambda的信息也很不错(当我说lambda ,我的意思是数学,我只能找到用某种语言编写的示例,而没有数学)

以下是对我有很大帮助的事情:

@ShuiRuTian关于m.join(q)返回Set<unknown> ,我假设--noImplicitAny也会发出警告?

我希望社区可以提供更多类似的用例,以检查当前方法在大多数情况下是否有效。

提供有关HKT /函数编程/ lambda的信息也很不错(当我说lambda ,我的意思是数学,我只能找到用某种语言编写的示例,而没有数学)

事不宜迟,我最近尝试创建一个通用的咖喱filter函数,并且我希望它执行以下操作:

const filterNumbers = filter(
    (item: number | string): item is number => typeof item === "number"
);

const array = ["foo", 1, 2, "bar"]; // (number | string)[]
const customObject = new CustomObject(); // CustomObject<number | string>

filterNumbers(array); // number[] inferred
filterNumbers(customObjectWithFilterFunction); // CustomObject<number> inferred

而且我没有实际执行此操作的方法,因为我需要一种方法告诉TypeScript“返回您收到的相同类型,但带有另一个参数”。 像这样:

const filter = <Item, FilteredItem>(predicate: (item: Item) => item is FilteredItem) =>
  <Filterable<~>>(source: Filterable<Item>): Filterable<FilteredItem> => source.filter(predicate);

@lukeshiru是的,本质上这是https://pursuit.purescript.org/packages/purescript-filterable/2.0.1/docs/Data.Filterable#v:filter
TypeScript中还有许多其他类似的HKT用例。

@isiahmeadows我尝试过。 你是对的。
@lukeshiru@raveclassic谢谢! 此功能似乎很合理。 阅读https://gcanti.github.io/fp-ts/learning-resources/后,我将对此进行查看

我被困住了,我不知道当前的``解决方法''是什么...
我正在尝试实现Chain规范

m['fantasy-land/chain'](f)

实现Chain规范的值还必须实现Apply规范。

a['fantasy-land/ap'](b)

我做了一个FunctorSimplex ,然后将其扩展了FunctorComplex然后又扩展了Apply但是现在我想将Apply扩展为Chain坏了...

所以我需要那个(下图和代码链接):

(我需要将类型传递给ApType以便在第12行中, Apply并非“硬编码”,而是通用的...还可以接受从IApply扩展的任何类型

Screenshot

永久链接到7ff8b9c中的代码段

打字稿
导出类型ApType = ap:Apply <(val:A)=> B>, )=> IApply ;

/ * [...] * /

导出接口IApply扩展了FunctorComplex {
/ ** Fantasy-land/ap :: Apply f => f a ~> f (a -> b) -> f b * /
ap:ApType ;
}
```

引用到堆栈溢出问题: TypeScript通用类型问题:所需的解决方法

@Luxcium直到TS支持更高类型的类型,才可以对其进行仿真。 您可能想看看那里是如何实现的:

在TS支持更高种类的类型之前,只能对其进行仿真。 您可能想看看那里是如何实现的

坦克很多@kapke这些天我可能对FP太pseudoFnAdd(15)(27) // 42我希望能够使用TypeScript编写pseudoType<someClassOrConstructor><number> // unknown但我是一个脚本小朋友,而不是某个在大学学习了很长时间的人,或者_

这些信息和讲座(阅读)非常感谢...

注意:我是用法语讲的,用法语单词_lecture(s)_的意思是_readings_,而不是“为了批评别人的行为而发给某人的生气或严肃的谈话” ...

以下是我想出的以下简单解决方法:没有PR,但并非在所有情况下都适用,但是我认为值得一提:

type AGenericType<T> = T[];

type Placeholder = {'aUniqueKey': unknown};
type Replace<T, X, Y> = {
  [k in keyof T]: T[k] extends X ? Y : T[k];
};

interface Monad<T> {
  map<A, B>(f: (a: A) => B): (v: Replace<T, Placeholder, A>) => Replace<T, Placeholder, B>;
  lift<A>(a: A): Replace<T, Placeholder, A>;
  join<A>(tta: Replace<T, Placeholder, Replace<T, Placeholder, A>>): Replace<T, Placeholder, A>;
}

function MONAD(m: Monad<AGenericType<Placeholder>>, f: (s: string) => number) {
  var a = m.map(f); // (v: string[]) => number[]
  var b = m.lift(1); // number[]
  var c = m.join([[2], [3]]); // number[]
}
此页面是否有帮助?
0 / 5 - 0 等级