Typescript: 建议:范围为数字类型

创建于 2017-04-30  ·  106评论  ·  资料来源: microsoft/TypeScript

定义类型时,可以指定由|分隔的多个数字。

type TTerminalColors = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;

允许将数字类型指定为范围,而不是列出每个数字:

type TTerminalColors = 0..15;
type TRgbColorComponent = 0..255;
type TUInt = 0..4294967295;

也许将..用于整数, ...用于浮点数。

interface Math {
  random(): 0...1
}
In Discussion Suggestion

最有用的评论

这个想法可以扩展到字符,例如"b".."d"将是"b" | "c" | "d" 。 指定字符集会更容易。

所有106条评论

这个想法可以扩展到字符,例如"b".."d"将是"b" | "c" | "d" 。 指定字符集会更容易。

我认为这可以扩展并使用类似 Haskell 范围的语法和语义。

| 语法 | 脱糖 |
|---------------------------|---------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------|
| type U = (e1..e3) | type U = \| e1 \| e1+1 \| e1+2 \| ...e3 \|
联合是never如果e1 > e3 |
| type U2 = (e1, e2..e3) | type U2 = \| e1 \| e1+i \| e1+2i \| ...e3 \| ,
其中增量ie2-e1

如果增量为正数或零,则当下一个元素大于e3时联合终止;
工会是never如果e1 > e3

如果增量为负,则当下一个元素小于e3时联合终止;
工会是never如果e1 < e3 。 |

@panuhorsmalahti如果您指定"bb".."dd"怎样?

@streamich

也许使用 .. 表示整数和 ... 表示浮点数。

我真的很喜欢像这样生成整数类型的想法,但我不明白浮点值是如何工作的。

@aluanhaddad说概率:

type TProbability = 0.0...1.0;

@streamich所以这种类型理论上有无限数量的可能居民?

@aluanhaddad实际上在 IEEE 浮点数中远非无限。 根据我的计算,它将有 1,065,353,217 名居民。

0.0...1.0 ? JS 使用 IEEE double,即 53 位动态范围。 如果要支持这一点,则范围必须是一流的类型,将其脱糖到联合将是不切实际的。

@jcready确实如此,但是,正如@fatcerberus指出的那样,将其作为联合类型来实现将

我以一种迂回的方式得到的是,这将在语言中引入一些离散类型与连续类型的概念。

将其实现为联合类型将非常广泛。

@aluanhaddad是的,但即使将无符号整数指定为联合也会非常昂贵:

type TUInt = 0..4294967295;

这确实需要一些引人注目的用例,因为今天联合的实施完全不适合实现如此大的联合。 如果你写这样的东西会发生什么

type UInt = 0..4294967295;
var x: UInt = ......;
if (x !== 4) {
  x;
}

将是联合类型0 | 1 | 2 | 3 | 5 | 6 | 7 | ...的实例化。

也许它只能对数字文字起作用。 任何非文字数字值都必须通过大于/小于比较来明确细化,然后才能被认为包含在该范围内。 整数范围还需要额外的Number.isInteger()检查。 这应该消除生成实际联合类型的需要。

@RyanCavanaugh减法类型? 🌞

否定类型,否定类型。

除了字符串之外的任何东西:

type NotAString = !string;

除零以外的任何数字:

type NonZeroNumber = number & !0;

@streamich减法类型由 #4183 涵盖

我的用例是:我想将参数输入为 0 或正数(它是数组索引)。

@RoyTinker我绝对认为这会很酷,但我不知道该用例是否有助于争论。
数组只是一个对象,升序索引只是一种约定。

let a = [];
for (let i = 0; i > -10; i -= 1) {
  a[i] = Math.random() * 10;
}

所以你最终仍然必须执行相同的检查

function withItem<T>(items: T[], index: number, f: (x: T) => void) {
  if (items[index]) {
    f(items[index]);
  }
}

这对于定义秒、分、小时、日、月等类型非常有用。

@Frikki这些单位的间隔足够有限,手工编写它们既实用又困难。

type Hour =
   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
   | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23;

@aluanhaddad但没有无符号整数:

type UInt = 0..4294967295;

嗯,这样的类型怎么样:

type Factorial<N extends number> = N > 2 ? Factorial<N - 1> * N : N;
type F1 = Factorial<1>; // 1
type F2 = Factorial<2>; // 1 | 2
type F3 = Factorial<3>; // 1 | 2 | 6
type FN = Factorial<number>; // 1 | 2 | 6 | ... 

使用@aleksey-bykov 评论中的*运算符:

type char = 0..255;
type word = char ** 2;
type int = word ** 2;
type bigint = int ** 2;

@streamich

@streamich ,一些评论:

  • 使用术语charword等可能会令人困惑,因为其他语言的人可能没有意识到静态定义和运行时行为之间的区别。
  • 您提议的语法没有考虑下限——如果它不为零怎么办?
  • 我对在环境/类型上下文中使用取幂运算符持谨慎态度,因为它已经被添加到 ES2016 中。

让我们完成类型系统图灵并享受我们遇到Ctrl + Shift + B停机问题

@aleksey-bykov 你肯定记得这个好问题😀

@aluanhaddad

这些单位 [时间单位] 处于足够有限的间隔内,因此手写它们是实用且极其困难的。

https://github.com/Microsoft/TypeScript/issues/15480#issuecomment -349270853

现在,以毫秒为单位:wink:

这个问题死了吗?

@Palid它被标记为[Needs Proposal]所以我对此表示怀疑。

尽管讨论很有趣,但我们大多数人都未能提供令人信服的现实世界用例。

https://github.com/Microsoft/TypeScript/issues/15480#issuecomment -324152700

__用例:__

  1. 您可以定义精确的 int 类型,以便 TypeScript 的子集可以编译为 WebAssembly 或其他一些目标。

  2. 另一个用例是UInt8ArrayInt8ArrayUint16Array等,当您从这些 TypeScript 读取或写入数据时可以检查错误。

const ab = new ArrayBuffer(1e3);
const uint8 = new UInt8Array(ab);

uint8[0] = 0xFFFFFFFF; // TSError: Number too big!
  1. 我在我的 OP 中提到了一些用例。

  2. 如果你实现了这一点,TypeScript 社区将提出数百万个用例。

我有一个非常有趣的用例,它实际上可能更接近这里提出的任何其他内容。
API 采用某个范围(在我的情况下为 5-30)之间的整数,我们需要为此实现一个 SDK。
对于 25 个值,在下面手动写入很乏味(虽然它可以自动化),但是成百上千呢?
type X = 5 | 6 | 7 | 8 | 9 | 10 ... | 30

@Palid这是我见过的最好和最直接的案例。

如果像5..30这样的范围被定义为5 | 6 | 7 ... | 30语法糖(或与其功能相同),我敢打赌这将是一个轻松的胜利。 这里它代表一个离散的整数范围。

也许可以通过使用带句点的数字来表示连续范围(而不是离散范围)—— 5.0..30.0

我实际上是在考虑看 Typescript 来实现打字保护
https://www.npmjs.com/package/memory-efficient-object

使用范围类型可以更容易地在类型检查时发现潜在的内存溢出

似乎我们真的不需要联合扩展,而更像是只比较范围的包含/排除范围定义

type TTerminalColors = int & [0,15];
// int && v >= 0 && v <= 15

这在控制电机时非常有用(例如使用 Johnny-Five),其速度范围为 0-255。

另一个用例:我正在使用 TypeScript 实现基于 Canvas 的绘图程序,并希望对不透明度(必须是 0.0 和 1.0 之间的数字)进行类型检查。

只是想……要正确实施,您必须全力以赴:

  • 支持运行时类型保护功能
  • x <= 10等条件进行类型缩小
  • 除了仅数字范围外,还支持string | number范围(因为x == 5true用于x === "5"
  • 甚至可能支持新的int类型(这将是number的子类型),并支持int -only 和string | int范围。 还可以为x|0等表达式键入缩小

好主意,但这将涉及很多内容!

也许我们不需要运行时类型保护。 相反,我们可以让编译时间一直保持

这将需要精心设计的分支预测

认为

type TTerminalColors = int & [0,15];

function A(color: TTerminalColors):void
{

}

A(15); // OK
var x = 15;
A(x); // OK

function B(value: int) : void
{
    A(value); // ERROR!!!
    if(value >= 0 && value <= 15)
        A(value); // OK, because we check that it is in the range of TTerminalColors
}

function C(value: int) : void
{
    if(value < 0)
        value = 0;
    if(value > 15)
        value = 15;

    A(value); // OK, because is clamped. But maybe too hard to implemented
}

function ClampInt(value: int): TTerminalColors
{
    if(value >= 0 && value <= 15)
        return value; // Same as B(int)

    if(value > 15)
        return 15;

    return 0;
}

我的用例:

export const ajax: (config: AjaxOptions) => void;

type Callback = Success | Error;
type Success = (data: any, statusText: string, xhr: XMLHttpRequest) => void;
type Error = (xhr: XMLHttpRequest, statusText: string) => void;

interface AjaxOptions {
  // ...
  statusCode: { [code: number]: Callback | Callback[] },
  // ...
}

能够限制statusCode选项中的键,以便在编译时可以确定状态代码对应于成功代码还是错误代码会很好:

interface AjaxOptions {
  // ...
  statusCode: {
    200..300: Success,
    400..600: Error
  },
  // ...
}

IMO 这应该被限制为浮点数和整数,并且不应该作为联合来实现,而是一个新的范围类型。 然后类型检查将非常简单:

if (val >= range.start && val < range.end) {
  return match;
} else {
  return no_match;
}

我们或许可以从 Ruby 的书中翻出一页,将..用于包含范围( [start, stop] ),将...用于非包含范围( [start, stop) )。

另一个用例是键入检查您的数据库记录:

另一个用例是类型检查 Lat/Lon 值。

这可以由#8665 的一般验证机制涵盖(不知道为什么它被作为副本关闭):

type TTerminalColors (n: number) => Math.floor(n) == n && n >= 0 && n <= 15;
type TRgbColorComponent (n: number) => Math.floor(n) == n && n >= 0 && n <= 255;
type TUInt (n: number) => n >= 0 && n <= 0..4294967295;

或者采用#4639,并假设无符号整数类型定义为uint

type TTerminalColors (n: uint) => n <= 15;
type TRgbColorComponent (n: uint) => n <= 255;

我的用例是全局坐标。 我想输入纬度和经度,以便它们只落在特定范围内(-90 到 90 和 -180 到 180)。

编辑:纬度和经度的范围为负

我的用例是固定大小数组的实现,其中大小是一个参数。

例如,我想为长度为 2 的字符串数组定义一个类型。

let d: FixedSizeArray<2, string>;
d = [ 'a', 'b' ]; // ok
d = [ 'a' ]; // type error
d = [ 'a', 'b', 'c' ]; // type error
d[0] = 'a1'; // ok
d[1] = 'b1'; // ok
d[2] = 'c1' // type error

使用当前版本的 TS,可以定义与上述“规范”非常接近的内容。 主要问题是成员访问:例如d[1] = 'b1'返回一个类型错误,即使它是正确的。 为了避免错误,必须在FixedSizeArray的定义中手工编译所有合法索引的列表,这很无聊。

如果我们有一个range类似操作keyof运营商,下面的类型定义应该解决的问题。

type FixedSizeArray<U extends number, T> = {
    [k in range(U)]: T;
} & { length: U };

其中range(N)range(0,N)的快捷方式。

给定数字文字(自然数)M,N,其中 M < N,

type r = range(M, N); 

相当于

type r = M | M+1 | ... | N-1

更普遍的是,我们是否能够定义谓词 lambda 并将它们用作类型。 例子:

predicate mypredicate = (x) => x > 1 && x < 10 
let x: mypredicate = 11 // not ok
let x: mypredicate = 5 // ok

专业人士的语法复杂度较低,因为 Lambda 已经可用,我们所需要的只是将它们用作类型的能力,无论如何类型检查都是特定于打字稿的(记住“超集到 Javascript”的理念)
缺点是谓词的复杂性将决定提供反馈的工具的性能。

确保数字/字符属于等差数列可能是一个很好的常见用例:

// a is the starting element d is the difference between two elements and L is the last element
const belongsToAP = (a, d, L) => {
  return (x) => {
    if(x < a || x > L) return false
    let n = ((x-a)/d) + 1
    if(Number.isInteger(n)) return true
    return false
  }
}

这将允许我们进行类型检查,例如:
谓词belongsToMyAP=belongsToAP(1,1,10)

let x : belongsToMyAP = 5 // ok
let y : belongsToMyAP = 7.2 // not ok

这也可以扩展到字符。

@Kasahs在#8665 中已经提出了类似的建议。

在“反映 API”用例中投入大量精力。 我正在为其编写包装函数的 REST 端点将 1..1000 范围内的整数作为参数。 如果数字不满足该约束,则会生成错误。

所以我正在写一个关于数字范围的建议,我遇到了这个我不知道如何处理的问题,所以我把它扔在那里供考虑。

因为 TypeScript 编译器是用 TypeScript 编写的,所以可以利用数字运算符的属性来改变范围类型。

// Syntax: x..y for an inclusive integer range.

let x: 0..10 = randomNumber(0, 10);
let y = x + 2; // Can deduce that y: 2..12.

这在分配给新变量时很好,但是突变呢?

let x: 0..10 = randomNumber(0, 10);
x += 2; // Error: 2..12 is not assignable to type 0..10 (upper bound is out of range).

这个错误在技术上是正确的,但在实践中处理起来会非常烦人。 如果我们想改变由具有范围返回类型的函数返回的变量,我们总是必须添加类型断言。

let x = randomNumber(0, 10) as number; // If randomNumber doesn't return a type assigable to number
// this will be an error, but it would still be annoying to have to sprinkle "as number"
// expressions everywhere.

如果我们忽略它,我们会得到以下不健全:

function logNumber0To10 (n: 0..10): void {
    console.log(n);
}

let x: 0..10 = randomNumber(0, 10);
x += 2; // Because we're ignoring mutations, x: 0..10, but the runtime value could be 11 or 12,
// which are outside the specified range...
logNumber0To10(x); // ...which means we lose type safety on this call.

对此的解决方法是能够在声明变量后更改其类型,因此示例 2 只需将 x 的类型更改为 2..12; 但我的第一直觉是这会在编译器中引入过多的开销并使用户感到困惑。

那么用户定义的函数和泛型呢?

// How to define this return type, seeing as we can't do math in types?
function increment<L extends number, H extends number> (x: L..H): (L + 1)..(H + 1);

关于如何处理上述问题的任何想法?

@JakeTunaley

突变呢?

为什么变异应该与任何其他分配不同?

这是我对提案的谦虚尝试。 该提案还要求添加其他类型。

  • Infinity类型

    • 只有Infinity

  • -Infinity类型

    • 只有价值-Infinity

  • NaN类型

    • 只有NaN

  • double类型

    • [-Number.MAX_VALUE, Number.MAX_VALUE][-1.7976931348623157e+308, 1.7976931348623157e+308]范围内的所有number

  • number只是Infinity|-Infinity|NaN|double
  • int类型

    • double子类型

    • [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER][-9007199254740991, 9007199254740991]Math.floor(x) === x范围内的所有numberx Math.floor(x) === x

    • 因此, 33.0值将是int类型

  • “有限文字”类型

    • 例如13.14145

    • 可能是doubleint子类型

  • “GtEq”类型表示为(>= x)

    • 其中x是有限文字, Infinity-Infinity

  • “LtEq”类型表示为(<= x)

    • 其中x是有限文字, Infinity-Infinity

  • “Gt”类型表示为(> x)

    • 其中x是有限文字, Infinity-Infinity

  • “Lt”类型表示为(< x)

    • 其中x是有限文字, Infinity-Infinity


GtEq 类型; (>= x)

  • (>= Infinity) = Infinity
  • (>= -Infinity) = -Infinity|double|Infinity
  • Infinity(>= [finite-literal])的子类型
  • (>= [finite-literal])double|Infinity的子类型
  • (>= NaN) = never
  • (>= int) = (>= -9007199254740991)
  • (>= double) = (>= -1.7976931348623157e+308)
  • (>= number) = number

Gt型; (> x)

  • (> Infinity) = never
  • (> -Infinity) = double|Infinity
  • Infinity(> [finite-literal])的子类型
  • (> [finite-literal])double|Infinity的子类型
  • (> NaN) = never
  • (> int) = (> -9007199254740991)
  • (> double) = (> -1.7976931348623157e+308)
  • (> number) = number

LtEq 类型; (<= x)

  • (<= Infinity) = -Infinity|double|Infinity
  • (<= -Infinity) = -Infinity
  • -Infinity(<= [finite-literal])的子类型
  • (<= [finite-literal])-Infinity|double的子类型
  • (<= NaN) = never
  • (<= int) = (<= 9007199254740991)
  • (<= double) = (<= 1.7976931348623157e+308)
  • (<= number) = number

LT型; (< x)

  • (< Infinity) = -Infinity|double
  • (< -Infinity) = never
  • -Infinity(< [finite-literal])的子类型
  • (< [finite-literal])-Infinity|double的子类型
  • (< NaN) = never
  • (< int) = (< 9007199254740991)
  • (< double) = (< 1.7976931348623157e+308)
  • (< number) = number

范围类型

请注意,虽然我们可以编写(>= Infinity)(> number)等内容,
结果类型不是范围类型; 它们只是其他类型的别名。

范围类型是其中之一,

  • (>= [finite-literal])
  • (> [finite-literal])
  • (<= [finite-literal])
  • (< [finite-literal])

我们允许像(> number)这样的语法在泛型中使用。


联合 GtEq/Gt 类型

当取两个 GtEq/Gt 类型的并集时,具有“更多”值的类型是结果,

  • (>= [finite-literal-A]) | (>= [finite-literal-B]) = ...

    • 如果[finite-literal-A] >= [finite-literal-B] ,则结果为(>= [finite-literal-B])

    • 否则,结果是(>= [finite-literal-A])

    • 例如(>= 3) | (>= 5.5) = (>= 3)因为(>= 3)(>= 5.5)的超类型

  • (>= [finite-literal-A]) | (> [finite-literal-B]) = ...

    • 如果[finite-literal-A] == [finite-literal-B] ,则结果为(>= [finite-literal-A])

    • 如果[finite-literal-A] > [finite-literal-B] ,则结果为(> [finite-literal-B])

    • 否则,结果是(>= [finite-literal-A])

  • (> [finite-literal-A]) | (> [finite-literal-B]) = ...

    • 如果[finite-literal-A] >= [finite-literal-B] ,则结果为(> [finite-literal-B])

    • 否则,结果是(> [finite-literal-A])

    • 例如(> 3) | (> 5.5) = (> 3)因为(> 3)(> 5.5)的超类型

还,

  • (>= A|B) = (>= A) | (>= B)
  • (> A|B) = (> A) | (> B)

    • 例如(> 4|3) = (> 4) | (> 3) = (> 3)

    • 例如(> number|3) = (> number) | (> 3) = number | (> 3) = number

联合 LtEq/Lt 类型

当取两个 LtEq/Lt 类型的并集时,具有“更多”值的类型是结果,

  • (<= [finite-literal-A]) | (<= [finite-literal-B]) = ...

    • 如果[finite-literal-A] <= [finite-literal-B] ,则结果为(<= [finite-literal-B])

    • 否则,结果是(<= [finite-literal-A])

    • 例如(<= 3) | (<= 5.5) = (<= 5.5)因为(<= 5.5)(<= 3)的超类型

  • (<= [finite-literal-A]) | (< [finite-literal-B]) = ...

    • 如果[finite-literal-A] == [finite-literal-B] ,则结果为(<= [finite-literal-A])

    • 如果[finite-literal-A] < [finite-literal-B] ,则结果为(< [finite-literal-B])

    • 否则,结果是(<= [finite-literal-A])

  • (< [finite-literal-A]) | (< [finite-literal-B]) = ...

    • 如果[finite-literal-A] <= [finite-literal-B] ,则结果为(< [finite-literal-B])

    • 否则,结果是(< [finite-literal-A])

    • 例如(< 3) | (< 5.5) = (< 5.5)因为(< 5.5)(< 3)的超类型

还,

  • (<= A|B) = (<= A) | (<= B)
  • (< A|B) = (< A) | (< B)

    • 例如(< 4|3) = (< 4) | (< 3) = (< 4)

    • 例如(< number|3) = (< number) | (< 3) = number | (< 3) = number


交点 GtEq/Gt 类型

当取两个 GtEq/Gt 类型的交集时,结果为“较少”值的类型,

  • (>= [finite-literal-A]) & (>= [finite-literal-B]) = ...

    • 如果[finite-literal-A] >= [finite-literal-B] ,则结果为(>= [finite-literal-A])

    • 否则,结果是(>= [finite-literal-B])

    • 例如(>= 3) & (>= 5.5) = (>= 5.5)因为(>= 5.5)(>= 3)的子类型

  • (>= [finite-literal-A]) & (> [finite-literal-B]) = ...

    • 如果[finite-literal-A] == [finite-literal-B] ,则结果为(> [finite-literal-B])

    • 如果[finite-literal-A] > [finite-literal-B] ,则结果为(>= [finite-literal-A])

    • 否则,结果是(> [finite-literal-B])

  • (> [finite-literal-A]) & (> [finite-literal-B]) = ...

    • 如果[finite-literal-A] >= [finite-literal-B] ,则结果为(> [finite-literal-A])

    • 否则,结果是(> [finite-literal-B])

    • 例如(> 3) & (> 5.5) = (> 5.5)因为(> 5.5)(> 3)的子类型

交叉口 LtEq/Lt 类型

当取两个 LtEq/Lt 类型的交集时,结果为“较少”值的类型,

  • (<= [finite-literal-A]) & (<= [finite-literal-B]) = ...

    • 如果[finite-literal-A] <= [finite-literal-B] ,则结果为(<= [finite-literal-A])

    • 否则,结果是(<= [finite-literal-B])

    • 例如(<= 3) & (<= 5.5) = (<= 3)因为(<= 3)(<= 5.5)的子类型

  • (<= [finite-literal-A]) & (< [finite-literal-B]) = ...

    • 如果[finite-literal-A] == [finite-literal-B] ,则结果为(< [finite-literal-B])

    • 如果[finite-literal-A] < [finite-literal-B] ,则结果为(<= [finite-literal-A])

    • 否则,结果是(< [finite-literal-B])

  • (< [finite-literal-A]) & (< [finite-literal-B]) = ...

    • 如果[finite-literal-A] <= [finite-literal-B] ,则结果为(< [finite-literal-A])

    • 否则,结果是(< [finite-literal-B])

    • 例如(< 3) & (< 5.5) = (< 3)因为(< 3)(< 5.5)的子类型


用例

  • 为了静态地确保整数可以适合 MySQL UNSIGNED INT数据类型,

    //TODO Propose numeric and range sum/subtraction/multiplication/division/mod/exponentiation types?
    function insertToDb (x : int & (>= 0) & (<= 4294967295)) {
      //Insert to database
    }
    
  • 为了静态地确保字符串具有给定的长度,

    function foo (s : string & { length : int & (>= 1) & (<= 255) }) {
      //Do something with this non-empty string that has up to 255 characters
    }
    
  • 要静态地确保类数组对象具有适当的length值,

    function foo (arr : { length : int & (>= 0) }) {
      //Do something with the array-like object
    }
    
  • 为了静态地确保我们只得到有限的数字,

    function foo (x : double) {
      //`x` is NOT NaN|Infinity|-Infinity
    }
    
  • 静态确保数组索引存在?

    function foo (arr : { [index : int & (>= 0) & (< 10)] : string }) {
      console.log(arr[0]); //OK
      console.log(arr[1]); //OK
      console.log(arr[2]); //OK
      console.log(arr[9]); //OK
      console.log(arr[10]); //Error
    }
    

我很想提出数字和范围加法/减法/乘法/除法/模/幂类型,但这似乎超出了这个问题的范围。

[编辑]
您可以重命名double并将其命名float但我只是认为double更准确地表示这是一个双精度浮点数。

[编辑]

将某些类型更改为never

是否可以让编译器进行流程分析?

假设有这个功能

function DoSomething(x : int & (>= 0) & (< 10)){
   // DoSomething
}

function WillError(x : int){
    DoSomething(x); // error; x is not >= 0 & < 10
}

function WillNotError(x : int){
    if(x >= 0 && x < 10)
        DoSomething(x); // not error by flow analysis
}

另一个用例:我有一个数字输入到一个表示百分比的函数。 我想将值限制在 0 和 1 之间。

我只是运行[...Array(256)].map((_,i) => i).join("|")来制作我迄今为止最丑的类型定义

非负整数和小数是可能的:

type ArrayT<T> = T extends (infer P)[] ? P : never;
type A = ArrayT<Range<5, 10>>;//5|6|7|8|9|10

范围:https ://github.com/kgtkr/typepark/blob/master/src/list.ts

“小数字”是指不使用第 3 阶段BigInt吗?

@Mouvedia符合编译器递归限制的数字

也许使用 .. 表示整数和 ... 表示浮点数。

我会说..应该意味着包含范围和...排除。 就像在 Ruby 中一样。 见http://rubylearning.com/satishtalim/ruby_ranges.html

我不喜欢使用单个时间段来区分包含范围和不包含范围

我可以跳进去吗? 我认为将此功能视为类型规范的单一增强功能可能不是最好的方法。

type range = 1:2:Infinity // 1, 3, 5… Infinity

这就是在许多 4gl 平台中定义数字范围的方式——特别是面向矩阵的平台。

您可以这样做,因为统一范围通常是最好的建模方法之一。

所以一个连续的范围是这样的:

type decmials = 1:10 // like 1 .. 10 with all decimals in between

如果只是整数:

type integers = 1:1:10 // 1, 2, 3, 4, 5, 6, 7, 8, 10

// OR

type integers = number.integers<1:10>

如果我们想玩弄语法:

type something = 1::10 // whatever use cases need today

如果这对标记器不友好,那么在我看来,这不是重点关注的正确阻塞优先级。 我指出这一点,希望不会看到一个解决方案限制我们进入下一个。

编辑:我没有考虑到包容性方面——在这里我们必须想知道我们是否做了足够的尽职调查来理解为什么当人们解决了这么多依赖于隐含包含每个数字的统一范围的问题时,这不是一个问题除非增量与范围的末尾不完全对齐。

有趣的是,您提出能够在范围类型中定义“步长”大小。

除了“浮点”(无固定步长)和“整数”(步长为 1,从整数开始)范围之外,我从未遇到过其他步长范围的实际用例。

所以,听说它在其他地方是一件很有趣的事情。 现在,我需要了解 4gl,因为我以前从未听说过它。


能够定义半开区间对于类数组对象很有用。 就像是,

interface SaferArray<T, LengthT extends integer & (>= 0)> {
    length : LengthT;
    [index in integer & (>= 0) & (< LengthT)] : T
}

如果我们只有包含范围,我们需要(<= LengthT - 1)但这只是不那么优雅

我可以跳进去吗? 我认为将此功能视为类型规范的单一增强功能可能不是最好的方法。

type range = 1:2:Infinity // 1, 3, 5… Infinity

这就是在许多 4gl 平台中定义数字范围的方式——特别是面向矩阵的平台。

您可以这样做,因为统一范围通常是最好的建模方法之一。

所以一个连续的范围是这样的:

type decmials = 1:10 // like 1 .. 10 with all decimals in between

如果只是整数:

type integers = 1:1:10 // 1, 2, 3, 4, 5, 6, 7, 8, 10

// OR

type integers = number.integers<1:10>

如果我们想玩弄语法:

type something = 1::10 // whatever use cases need today

如果这对标记器不友好,那么在我看来,这不是重点关注的正确阻塞优先级。 我指出这一点,希望不会看到一个解决方案限制我们进入下一个。

编辑:我没有考虑到包容性方面——在这里我们必须想知道我们是否做了足够的尽职调查来理解为什么当人们解决了这么多依赖于隐含包含每个数字的统一范围的问题时,这不是一个问题除非增量与范围的末尾不完全对齐。

嗯看起来像它在 Haskell 中的处理方式。 我认为这很好。 生成器也将允许惰性求值。

并不是说您不能使用任何其他语法进行惰性评估 =x

有趣的是,您提出能够在范围类型中定义“步长”大小。

我认为范围是开始、结束和增量。 因此,如果增量四舍五入到最后,那么它就包含在内。 数组索引和长度作为范围函数可以是一个数组 0:1:9 有 10 个索引步长(长度)。 所以这里的范围可以方便地integer & 0:9因为类型系统可以更容易地通过组合这两个表达式来推断。

4GL 真的是一个通用标签,对我来说主要是 MatLab

我的观点是,只有包含范围会使在泛型中使用它变得更加困难(除非您实现 hack-y 类型级数学运算)。

因为,您现在不仅需要“长度”作为类型参数,还需要长度和最大索引。 并且最大索引必须等于 length-1。

而且,再一次,除非您实现那些 hack-y 类型级别的数学运算,否则您实际上无法检查情况是否如此

@AnyhowStep我在想最好的方法来表达你的担忧——但也许首先澄清一下会有所帮助:

如果我们将包含或不包含特别适用于任何 n-1 问题(如这个索引/计数场景)放在一边,假设以某种方式独立解决(只是幽默的想法),是否还有其他场景的数字范围会仍然需要较少的声明性和/或非常规语法来正确描述?

我得到的是 2 个独立的方面,您需要通常与该域的期望一致的数字范围,并且您还需要索引/计数类型的谓词......等等。

仅正数。

declare let x : (> 0);
x = 0.1; //OK
x = 0.0001; //OK
x = 0.00000001; //OK
x = 0; //Error
x = 1; //OK
x = -1; //Error

仅包含范围,

declare let x : epsilon:epsilon:Infinity; //Where epsilon is some super-small non-zero, positive number

正数,不包括Infinity

declare let x : (> 0) & (< Infinity);

仅包含范围,

const MAX_FLOAT : 1.7976931348623157e+308 = 1.7976931348623157e+308;
declare let x : epsilon:epsilon:MAX_FLOAT;

此外,与当前讨论无关,但这是我真正想要数字范围类型的另一个原因。

我的很多日常工作只是将一堆不同的软件系统拼凑在一起。 许多这些系统对具有相同含义的数据有不同的要求。

例如,(systemA,值介于 1 和 255 之间),(systemB,值介于 3 和 73 之间)等。
例如(systemC.字符串长度7-88)、(systemD,字符串长度9-99)、(systemE,字符串长度2-101)等。

现在,我需要仔细记录所有这些单独的要求,并确保来自一个系统的数据可以正确映射到另一个系统。 如果它没有映射,我需要找出解决方法。

我只是人类。 我犯错了。 我没有意识到数据有时无法映射。 范围检查失败。

使用数字范围类型,我最终可以声明每个系统期望的范围并让编译器为我进行检查。


例如,我遇到过这样一种情况,我使用的 API 对所有字符串值的字符串长度限制为 10k。 好吧,我无法告诉 TypeScript 检查进入 API 的所有字符串是否<= 10k 字符串长度。

我有一个运行时错误,而不是一个很好的编译错误,TS 可以去,

`string` is not assignable to `string & { length : (<= 10000) }`

@AnyhowStep我希望你能理解我的意图只是为了确保如果称为“范围作为数字类型”的东西只是符合更传统用户的共同期望(即有人转向 TS 想知道为什么范围强调区间内的包容性)

老实说,我认为用例应该始终驱动功能,并认为与索引和长度相关的问题是我们许多人有时真的会错过的问题。 所以我只想在正确的标签下解决这个问题——它是数字类型的东西还是索引类型的东西? 我不知道确切的答案,但我很犹豫地认为将它作为数字类型的事情来解决不会无意中为数字类型的用户带来更多的问题,因为他们对这些方面没有相同的理解。

那么,在这一点上,我想知道还有什么地方可以解决这样的问题对每个人都有意义,想法?

我正在处理一个传递字节数组的 API,所以我想定义一个字节类型:

type byte = 0x00..0xFF
type bytes = byte[]

这在使用Uint8Array时也很有用。

如果您和我一样,并且对获取目前可以实际使用的范围类型不耐烦,那么这里有一个正在运行的范围类型的代码片段。

TL;博士,
范围类型现在可以工作

interface CompileError<_ErrorMessageT> {
    readonly __compileError : never;
}
///////////////////////////////////////////////
type PopFront<TupleT extends any[]> = (
    ((...tuple : TupleT) => void) extends ((head : any, ...tail : infer TailT) => void) ?
    TailT :
    never
);
type PushFront<TailT extends any[], FrontT> = (
    ((front : FrontT, ...tail : TailT) => void) extends ((...tuple : infer TupleT) => void) ?
    TupleT :
    never
);
type LeftPadImpl<TupleT extends any[], ElementT extends any, LengthT extends number> = {
    0 : TupleT,
    1 : LeftPad<PushFront<TupleT, ElementT>, ElementT, LengthT>
}[
    TupleT["length"] extends LengthT ?
    0 :
    1
];
type LeftPad<TupleT extends any[], ElementT extends any, LengthT extends number> = (
    LeftPadImpl<TupleT, ElementT, LengthT> extends infer X ?
    (
        X extends any[] ?
        X :
        never
    ) :
    never
);
type LongerTuple<A extends any[], B extends any[]> = (
    keyof A extends keyof B ?
    B :
    A
);

///////////////////////////////////////////////////////
type Digit = 0|1|2|3|4|5|6|7|8|9;
/**
 * A non-empty tuple of digits
 */
type NaturalNumber = Digit[];

/**
 * 6 - 1 = 5
 */
type SubOne<D extends Digit> = {
    0 : never,
    1 : 0,
    2 : 1,
    3 : 2,
    4 : 3,
    5 : 4,
    6 : 5,
    7 : 6,
    8 : 7,
    9 : 8,
}[D];

type LtDigit<A extends Digit, B extends Digit> = {
    0 : (
        B extends 0 ?
        false :
        true
    ),
    1 : false,
    2 : LtDigit<SubOne<A>, SubOne<B>>
}[
    A extends 0 ?
    0 :
    B extends 0 ?
    1 :
    2
];


//false
type ltDigit_0 = LtDigit<3, 3>;
//true
type ltDigit_1 = LtDigit<3, 4>;
//false
type ltDigit_2 = LtDigit<5, 2>;


/**
 * + Assumes `A` and `B` have the same length.
 * + Assumes `A` and `B` **ARE NOT** reversed.
 *   So, `A[0]` is actually the **FIRST** digit of the number.
 */
type LtEqNaturalNumberImpl<
    A extends NaturalNumber,
    B extends NaturalNumber
> = {
    0 : true,
    1 : (
        LtDigit<A[0], B[0]> extends true ?
        true :
        A[0] extends B[0] ?
        LtEqNaturalNumberImpl<
            PopFront<A>,
            PopFront<B>
        > :
        false
    ),
    2 : never
}[
    A["length"] extends 0 ?
    0 :
    number extends A["length"] ?
    2 :
    1
];
type LtEqNaturalNumber<
    A extends NaturalNumber,
    B extends NaturalNumber
> = (
    LtEqNaturalNumberImpl<
        LeftPad<A, 0, LongerTuple<A, B>["length"]>,
        LeftPad<B, 0, LongerTuple<A, B>["length"]>
    > extends infer X ?
    (
        X extends boolean ?
        X :
        never
    ) :
    never
);

//false
type ltEqNaturalNumber_0 = LtEqNaturalNumber<
    [1],
    [0]
>;
//true
type ltEqNaturalNumber_1 = LtEqNaturalNumber<
    [5,2,3],
    [4,8,9,2,3]
>;
//false
type ltEqNaturalNumber_2 = LtEqNaturalNumber<
    [4,8,9,2,3],
    [5,2,3]
>;
//true
type ltEqNaturalNumber_3 = LtEqNaturalNumber<
    [5,2,3],
    [5,2,3]
>;
//true
type ltEqNaturalNumber_4 = LtEqNaturalNumber<
    [5,2,2],
    [5,2,3]
>;
//false
type ltEqNaturalNumber_5 = LtEqNaturalNumber<
    [5,1],
    [2,5]
>;
//false
type ltEqNaturalNumber_6 = LtEqNaturalNumber<
    [2,5,7],
    [2,5,6]
>;

type RangeLt<N extends NaturalNumber> = (
    number &
    {
        readonly __rangeLt : N|undefined;
    }
);
type StringLengthLt<N extends NaturalNumber> = (
    string & { length : RangeLt<N> }
);

type AssertStringLengthLt<S extends StringLengthLt<NaturalNumber>, N extends NaturalNumber> = (
    LtEqNaturalNumber<
        Exclude<S["length"]["__rangeLt"], undefined>,
        N
    > extends true ?
    S :
    CompileError<[
        "Expected string of length less than",
        N,
        "received",
        Exclude<S["length"]["__rangeLt"], undefined>
    ]>
);
/**
 * String of length less than 256
 */
type StringLt256 = string & { length : RangeLt<[2,5,6]> };
/**
 * String of length less than 512
 */
type StringLt512 = string & { length : RangeLt<[5,1,2]> };

declare function foo<S extends StringLengthLt<NaturalNumber>> (
    s : AssertStringLengthLt<S, [2,5,6]>
) : void;

declare const str256 : StringLt256;
declare const str512 : StringLt512;

foo(str256); //OK!
foo(str512); //Error

declare function makeLengthRangeLtGuard<N extends NaturalNumber> (...n : N) : (
    (x : string) => x is StringLengthLt<N>
);

if (makeLengthRangeLtGuard(2,5,6)(str512)) {
    foo(str512); //OK!
}

declare const blah : string;
foo(blah); //Error

if (makeLengthRangeLtGuard(2,5,5)(blah)) {
    foo(blah); //OK!
}

if (makeLengthRangeLtGuard(2,5,6)(blah)) {
    foo(blah); //OK!
}

if (makeLengthRangeLtGuard(2,5,7)(blah)) {
    foo(blah); //Error
}

操场

它使用这里的CompileError<>类型,
https://github.com/microsoft/TypeScript/issues/23689#issuecomment -512114782

AssertStringLengthLt<>类型是神奇发生的地方

使用类型级加法,您可以拥有str512 + str512并获得str1024

https://github.com/microsoft/TypeScript/issues/14833#issuecomment -513106939

看着@AnyhowStep解决方案,我想我有更好的临时解决方案。 还记得类型守卫吗?:

/**
 * Just some interfaces
 */
interface Foo {
    foo: number;
    common: string;
}

interface Bar {
    bar: number;
    common: string;
}

/**
 * User Defined Type Guard!
 */
function isFoo(arg: any): arg is Foo {
    return arg.foo !== undefined;
}

/**
 * Sample usage of the User Defined Type Guard
 */
function doStuff(arg: Foo | Bar) {
    if (isFoo(arg)) {
        console.log(arg.foo); // OK
        console.log(arg.bar); // Error!
    }
    else {
        console.log(arg.foo); // Error!
        console.log(arg.bar); // OK
    }
}

doStuff({ foo: 123, common: '123' });
doStuff({ bar: 123, common: '123' });

所以看看下面的代码:

class NumberRange {
    readonly min: number;
    readonly max: number;
    constructor(min:number, max:number, ) {
        if (min > max) { 
            throw new RangeError(`min value (${min}) is greater than max value (${max})`);
        } else {
            this.min = min;
            this.max = max;
        }
    }
    public isInRange = (num: number, explicit = false): boolean => {
        let inRange: boolean = false;
        if (explicit === false) {
            inRange = num <= this.max && num >= this.min;
        } else {
            inRange = num < this.max && num > this.min;
        }
        return inRange;
    };
}
const testRange = new NumberRange(0, 12);
if(testRange.isInRange(13)){
    console.log('yay')
}else {
  console.log('nope')
}

这只是一个想法,但使用类型保护可以使用范围。

这里的问题是你不能这样做,

declare const a : number;
declare let b : (>= 5);
const testRange = new NumberRange(12, 20);
if (testRange.isInRange(a)) {
  b = a; //ok
} else {
  b = a; //compile error
}

单独的类型保护不是一个令人满意的解决方案

此外,您的示例甚至不使用类型保护。


我上面那个愚蠢的 hacky 玩具示例允许您将一个范围类型分配给另一个范围类型(​​通过函数参数),如果它的边界在另一个范围内。


此外,在原语上使用的标记类型、名义类型和值对象通常表明类型系统不够表达。

我讨厌我使用标签类型的愚蠢玩具示例,因为它非常不符合人体工程学。 您可以参考这个冗长的评论,了解如何让范围成为原始类型更好。

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -548249683

您可以使用当前的 Typescript 版本来实现这一点:

// internal helper types
type IncrementLength<A extends Array<any>> = ((x: any, ...xs: A) => void) extends ((...a: infer X) => void) ? X : never;
type EnumerateRecursive<A extends Array<any>, N extends number> = A['length'] extends infer X ? (X | { 0: never, 1: EnumerateRecursive<IncrementLength<A>, N> }[X extends N ? 0 : 1]) : never;

// actual utility types
export type Enumerate<N extends number> = Exclude<EnumerateRecursive<[], N>, N>;
export type Range<FROM extends number, TO extends number> = Exclude<Enumerate<TO>, Enumerate<FROM>>;

// usage examples:
type E1 = Enumerate<3>; // hover E1: type E1 = 0 | 1 | 2
type E2 = Enumerate<10>;  // hover E2: type E2 = 0 | 1 | 3 | 2 | 4 | 5 | 6 | 7 | 8 | 9

type R1 = Range<0, 5>; // hover R1: type R1 = 0 | 1 | 3 | 2 | 4
type R2 = Range<5, 11>; // hover R2: type R2 = 10 | 5 | 6 | 7 | 8 | 9

我遵循了包含开始索引和独占结束索引的惯例,但您可以根据自己的需要进行调整。

vs 代码悬停提示在注释中。

请注意,悬停提示中的数字是随机排序的。

image

可悲的是,它最多只能使用大约 10 个元素:(

编辑:似乎Enumerate最多只能处理15递归(0 - 14)

@死神92
使用这种方法,Enumerate 最多可处理 40 个。
这仍然是一个限制,但在实践中可能没有那么苛刻。

type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;
type EnumerateInternal<A extends Array<unknown>, N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>, N> }[N extends A['length'] ? 0 : 1];
export type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;
export type Range<FROM extends number, TO extends number> = Exclude<Enumerate<TO>, Enumerate<FROM>>;

type E1 = Enumerate<40>;
type E2 = Enumerate<10>;
type R1 = Range<0, 5>;
type R2 = Range<5, 34>;

现在它碰巧以某种方式神奇地排序;)。

我的典型用例使用范围如[1, 255][1, 2048][1, 4096][20, 80]等。创建大型联合类型会使 TS 崩溃/变慢. 但这些解决方案肯定适用于“较小”的范围

@AnyhowStep
知道递归计数是一个限制,我们应该找到在类型定义内的单个非递归操作中执行数字除/乘以 2 的方法来实现这些范围。

性能仍然是一个问题。 由于这个原因,我之前不得不从大型应用程序中剔除有用的联合类型 - 虽然这可能是一个有趣的练习,但它绝对不是一个解决方案。

我猜仍然没有完美的解决方案?

我希望这可以按以下一般方式完成(但很难)。

type X => (number >= 50 && number > 60 || number = 70) || (string.startsWith("+"))

(即这里可以使用普通的javascript语句/函数,除了变量被类型替换)

在我的理解中,现代语言 = 正常逻辑 + 元逻辑,其中元逻辑 = 代码检查 + 代码生成。 很多作品只是试图以一种优雅的方式合并元逻辑语法。

也许我们可以以某种方式假设范围类型不是某种糖,而是核心概念?

// number literal extend `number` despite the fact 
// that `number` is not union of all number literals
type NumberLiteralIsNumber = 5 extends number ? true : false // true

// so if we will define Range (with some non-existing syntax which is clearly should be done in lib internals)
type Range<MIN extends number, MAX extends number> = MAX > MIN ? 5 and 2NaN : MAX === MIN ? MAX : MIN..MAX

// and assume that `number` is Range<-Infinity, +Infinity>
type NumberIsInfinitRange = number extends Range<-Infinity, +Infinity> ?
    Range<-Infinity, +Infinity> extends number ? true : false :
    false // true

// following things will be true
type AnyRangeIsNumber<T extends number, K extends Number> = 
    Range<T, K> extends number ? true : false // true
type NestedRangeIsNumber<T extends number, K extends number, S extends number> =
    Range<T, Range<K, S>> extends number ? true : false // true

为了完成这个,我们需要在某些情况下声明行为

  1. Range<T, T> === T按定义
  2. Range<5, 1> === NaN按定义
  3. Range<NaN, T> === Range<T, NaN> === NaN作为这样的值不存在
  4. Range<1 | 2, 5> === Range<2, 5>大数作为第一类型参数的可能性限制了范围更短
  5. Range<1, 4 | 5> === Range<1, 4>小数作为第二类型参数的可能性限制了范围更短
  6. 1.5 extends Range<1, 2>根据定义应该是真的
  7. Range<Range<A, B>, C> === NaN extends Range<A, B> ? NaN : Range<B, C>跟在 5 和 3 之后
  8. Range<A, Range<B, C>> === NaN extends Range<B, C> ? NaN : Range<A, B>跟在 6 和 3 之后

还有一些关于 Ranges 内 Ranges 的信息:

type RangeIsInsideRange<T extends Range<any, any>, K extends Range<any, any>> = 
    T extends Range<infer A, infer B> 
        ? K extends Range<infer C, infer D> 
            ? NaN extends Range<A, C> 
                ? false 
                : NaN extends Range<B, D> 
                    ? false 
                    : true 
            : never
        : never

范围子类型甚至可以以通用方式定义,作为类型级别的比较函数(a: T, b: T) => '<' | '=' | '>'

...是否有在类型级别使用常规 JS 函数的建议?

本身没有建议,但我之前写过一个快速原型。 不幸的是,人们对 IDE 性能和输入清理存在很大的担忧。

@yudinns问题是 Range 至少以这个名称存在,所以它可能会令人困惑,请参阅: https : <1, n+2> - 从 1 个或更复杂的方程开始的第 2 步。

这个 TC39 提案可能会在实施之前达到第 4 阶段。
票是3年的。

建议:正则表达式验证的字符串类型: https :

(相关的 SO 问题:https://stackoverflow.com/questions/3895478/does-javascript-have-a-method-like-range-to-generate-a-range-within-the-supp)

如果你能做一些像number in range例如if 1 in 1..2或包容性的if 1 in 1..=2 rust 处理它也会很酷https://doc.rust-lang.org/reference/表达式/范围-expr.html 。 那会节省很多空间

python 和 rust 没有类型化范围功能吗?
我们能否只用其他语言重新实现现有的解决方案,而不是重新发明轮子或使用字符串、浮点数等使其更加通用,而主要目的和最大用例是数字范围。 我们可以稍后在需要时添加更多

我不止一次遇到过这个问题,来到这里寻找一种方法来定义一个介于 0...1 之间的分数的类型 - 这正在https://news.ycombinator.com/item?id=上讨论

@andrewphillipo我也看到有人在讨论这个问题,并从堆栈交换的一个答案中了解了术语单位间隔,这似乎是在通用上下文中指代该特定范围的正确方法。

如果在打字稿中实现了作为类型的范围,也许UnitInterval可以定义为一个全局类型,以鼓励我们在编程中经常使用的一些东西的通用命名法。

是的 UnitInterval 是范围 0...1 的正确名称,因此它对于类型来说是正确的! 对于数字的命名仍然不正确,因此如果可以使用这种类型更精确地描述我们的代码,那就太好了——这在 Rust 的类型系统中是如何工作的——它只是一个守卫还是?

如果CantorSpace不是太大,我认为这将是计算机理解的[0, 1]之间“所有”实数的“范围”的公平定义。 在编译时将这些分配给值不能通过 javascript 的Math.floorMath.ceil的下限和上限推断,因为Math.ceil(0) === 0Math.floor(1) === 1是不幸的。

如果使用所有数字(0, 1)的集合,它会使用上面的示例工作,但是排除在日常语言中以百分比形式引用的值有点不好。 如果可能,让编译器以某种方式包含边界会很好,也许通过严格的===检查边缘情况 0 和 1。

或者也许使用1~~3表示整数和0.1~0.5表示浮点数?

~已经被一元按位运算符采用。

为什么我们需要谈论那些该死的浮点数,我们的问题是整数。 99.99% 是整数问题 0.001% 是浮点问题。
没有容易实现的通用解决方案,所以让我们使用大多数编程语言中看到的 0..10 的数字范围,就像几年前已经实现的那样。

停止浮动说话

也许我们可以拆分法案并针对 int 案例扩展此提案:
https://gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c

对于浮动,我目前正在根据本期中的一些想法(主要是@AnyhowStep;基本上是具有开放间隔类型的能力)制定不同的设计方案。

保持简单
x..y仅适用于整数。
0.1..2会报错
1..2.1也会报错
以及任何可检测的非整数。
哎呀复制实现逻辑
https://en.m.wikibooks.org/wiki/Ada_Programming/Types/range
或者
https://kotlinlang.org/docs/reference/ranges.html
或者
https://doc.rust-lang.org/reference/expressions/range-expr.html
并收工。

✅无需重新发明轮子
✅无需考虑浮动
✅跨语言的一致性
✅测试和复制现有来源的稳定代码

解决浮点数的努力是值得的,因为通过将类型值硬编码为每个固定整数 0..1000 的并集,基于“int”的类型检查对于小于 1000 的数字来说是微不足道的,所以说“没有必要”“重新-发明轮子”有点讽刺。 为什么不使用已经适用于简单用例的方法?

我宁愿留出一些空间来允许理解诸如无理数之类的东西的类型定义。 在使用弧度时在编译时检测不变量的能力将是一个有趣的用例。 支持 τ 作为范围值边界的参数是一个很好的目标,可以声称此功能按预期的 imo 工作。

请记住,javascript 中没有明确的integer 。 一切都只是一个number ,它被存储为一个浮点数。 如果实现了范围类型运算符,它应该处理number可以处理的任何值。

@aMoniker取决于explicit的定义: BigInt

即使对于 bigint,也存在联合无法解决的用例,例如确保 bigint 适合 MySQL 有符号/无符号 bigint

为这个特性创建一个intrinsic范围类型怎么样?

lib.es5.d.ts (或lib.es2020.bigint.d.ts包括对 bigint 的支持)
type GreaterThan<N extends number | bigint> = intrinsic
type GreaterThanOrEqualTo<N extends number | bigint> = GreaterThan<N> | N
type LessThan<N extends number | bigint> = intrinsic
type LessThanOrEqualTo<N extends number | bigint> = LessThan<N> | N

/**
 * prevent `GreaterThan` and `LessThan` from desugaring
 * (in the same way that `number` does _not_ desugar to `-Infinity | ... | Infinity`)
 */
用户空间
type GreaterThanOrEqualTo2 = GreaterThanOrEqualTo<2>
type LessThan8 = LessThan<8>

type GreaterThanOrEqualTo2_And_LessThan8 = GreaterThanOrEqualTo2 & LessThan8
type LessThan2_Or_GreaterThanOrEqualTo8 = LessThan<2> | GreaterThanOrEqualTo<8> // inverse of `GreaterThanOrEqualTo2_And_LessThan8` (would be nice to be able to just do `Exclude<number | bigint, GreaterThanOrEqualTo2_And_LessThan8>` but that might be wishful thinking)
type LessThan2_And_GreaterThanOrEqualTo8 = LessThan<2> & GreaterThanOrEqualTo<8> // `never`
type GreaterThanOrEqualTo2_Or_LessThan8 = GreaterThanOrEqualTo2 | LessThan8 // `number | bigint`

type RangesAreNumbersOrBigIntsByDefault = LessThan8 extends number | bigint ? true : false // `true` (the user could always narrow this on a per-range basis, e.g. `LessThan8 & number`)
type RangesAcceptUnions = LessThan<7n | 7.5 | 8> // `LessThan<8>`
type RangesAcceptOtherRanges1 = LessThan<LessThan8> // `LessThan8`
type RangesAcceptOtherRanges2 = LessThan<GreaterThanOrEqualTo2> // `number | bigint`
type RangesSupportBeingInAUnion = (-6 | 0.42 | 2n | 2 | 3) | LessThan<2> // `LessThan<2> | 2n | 2 | 3`
type RangesSupportBeingInAnIntersection = (-6 | 0.42 | 2n | 2 | 3) & LessThan<2> // `-6 | 0.42`
type RangesSupportBeingInAUnionWithOtherRanges = LessThan<2> | LessThan8 // `LessThan8`
type RangesSupportBeingInAnIntersectionWithOtherRanges = LessThan<2> & LessThan8 // `LessThan<2>`

@瑞安卡瓦诺

这确实需要一些引人注目的用例,因为今天联合的实施完全不适合实现如此大的联合。 如果你写这样的东西会发生什么......

如果没有 int 类型,以下内容将是不合理的:

const arr: string[] = ['a', 'b', 'c']
const item = arr[1.01]  // Typescript inferred this as string but actually it is undefined
console.log(item)  // Will print undefined, we miss inferred the type

具有以下类型将防止此问题:

type TUInt = 0..4294967295;

另一个使用Int32Int64类型的用例是当人们开始用它来注释他们的代码时......将打开与其他语言更好的互操作性的大门......几乎都是静态类型的语言有一个整数类型:Java、C#、C、Rust、F#、Go 等。

例如,如果我想从 C# 调用用 TypeScript 编写的 npm 库,则有一些库采用 TypeScript 定义并在 C# 中为我创建一个接口,但问题是number类型是float它不能用于在 C# 中索引数组而不用外壳……等等。

其他用例:更容易在语言之间进行转换、性能优化......等

请注意,另一个有用的情况是与 WebAssembly 的交互,它具有非常明确的单独数字类型(我想它被简要提到为 C# 用例,但想澄清它的适用范围远不止于此)。

UPD:Nvm,我看到它在https://github.com/microsoft/TypeScript/issues/15480#issuecomment -365420315 中被提及,Github 已“帮助”将其隐藏为“隐藏项目”。

+1

此页面是否有帮助?
0 / 5 - 0 等级