Typescript: Saran: Rentang sebagai tipe Nomor

Dibuat pada 30 Apr 2017  ·  106Komentar  ·  Sumber: microsoft/TypeScript

Saat mendefinisikan tipe, seseorang dapat menentukan beberapa angka yang dipisahkan oleh | .

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

Izinkan untuk menentukan jenis nomor sebagai rentang, alih-alih mencantumkan setiap nomor:

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

Mungkin menggunakan .. untuk bilangan bulat dan ... untuk float.

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

Komentar yang paling membantu

Ide ini dapat diperluas ke karakter, misalnya "b".."d" akan menjadi "b" | "c" | "d" . Akan lebih mudah untuk menentukan set karakter.

Semua 106 komentar

Ide ini dapat diperluas ke karakter, misalnya "b".."d" akan menjadi "b" | "c" | "d" . Akan lebih mudah untuk menentukan set karakter.

Saya pikir ini dapat diperluas dan menggunakan sintaks seperti dan semantik seperti rentang Haskell.

| Sintaks | Tanpa gula |
|-----------------------|----------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------|
| type U = (e1..e3) | type U = \| e1 \| e1+1 \| e1+2 \| ...e3 \|
Union adalah never jika e1 > e3 |
| type U2 = (e1, e2..e3) | type U2 = \| e1 \| e1+i \| e1+2i \| ...e3 \| ,
di mana kenaikannya, i , adalah e2-e1 .

Jika kenaikannya positif atau nol, penyatuan berakhir ketika elemen berikutnya akan lebih besar dari e3 ;
serikat adalah never jika e1 > e3 .

Jika kenaikannya negatif, penyatuan berakhir ketika elemen berikutnya akan kurang dari e3 ;
serikat adalah never jika e1 < e3 . |

@panuhorsmalahti Bagaimana jika Anda menentukan "bb".."dd" ?

@streamich

Mungkin menggunakan .. untuk bilangan bulat dan ... untuk pelampung.

Saya sangat menyukai ide menghasilkan tipe integral seperti ini, tetapi saya tidak melihat bagaimana nilai floating point dapat bekerja.

@aluanhaddad Katakanlah probabilitas:

type TProbability = 0.0...1.0;

@streamich sehingga tipe itu memiliki kemungkinan jumlah penghuni yang secara teoritis tak terbatas?

@aluanhaddad sebenarnya akan jauh dari tak terbatas di IEEE floating point. Itu akan memiliki 1.065.353.217 penduduk menurut perhitungan saya.

0.0...1.0 ? JS menggunakan IEEE ganda, itu 53 bit rentang dinamis. Jika itu harus didukung, rentang harus menjadi tipe kelas satu, menganggap bahwa untuk persatuan akan sangat tidak praktis.

@jcready memang tetapi, seperti yang ditunjukkan oleh @fatcerberus , menyadarinya sebagai tipe serikat pekerja akan sangat ekspansif.

Apa yang saya maksudkan, secara tidak langsung, adalah bahwa ini akan memperkenalkan beberapa gagasan tentang tipe diskrit vs. kontinu ke dalam bahasa.

menyadarinya sebagai tipe serikat pekerja akan sangat ekspansif.

@aluanhaddad Ya, tetapi bahkan menentukan bilangan bulat yang tidak ditandatangani sebagai gabungan akan sangat mahal:

type TUInt = 0..4294967295;

Ini benar-benar membutuhkan beberapa kasus penggunaan yang menarik, karena penerapan serikat pekerja saat ini sama sekali tidak cocok untuk mewujudkan serikat pekerja sebesar ini. Sesuatu yang akan terjadi jika Anda menulis sesuatu seperti ini

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

akan menjadi instantiasi dari tipe serikat 0 | 1 | 2 | 3 | 5 | 6 | 7 | ... .

Mungkin itu hanya bisa bekerja melawan literal angka. Nilai angka non-literal apa pun harus secara eksplisit disempurnakan dengan perbandingan yang lebih besar/kurang dari sebelum dianggap menghuni rentang. Rentang bilangan bulat juga memerlukan pemeriksaan Number.isInteger() . Ini harus menghilangkan kebutuhan untuk menghasilkan jenis serikat yang sebenarnya.

@RyanCavanaugh Jenis pengurangan? 🌞

Tipe negatif, tipe negasi.

Apa pun kecuali string:

type NotAString = !string;

Setiap nomor kecuali nol:

type NonZeroNumber = number & !0;

Jenis pengurangan

Kasus penggunaan saya adalah: Saya ingin mengetikkan parameter sebagai 0 atau angka positif (ini adalah indeks array).

@RoyTinker Saya pasti berpikir ini akan keren tapi saya tidak tahu apakah use case itu membantu argumen.
Array hanyalah sebuah objek dan indeks menaik hanyalah sebuah konvensi.

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

jadi Anda akhirnya masih harus melakukan pemeriksaan yang sama

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

Ini akan sangat berguna untuk mendefinisikan tipe seperti detik, menit, jam, hari, bulan, dll.

@Frikki unit-unit itu berada pada interval yang cukup terbatas sehingga praktis dan sangat sulit untuk menulisnya dengan tangan.

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 Tapi tidak ada int yang tidak ditandatangani:

type UInt = 0..4294967295;

meh, bagaimana dengan tipe seperti ini:

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 | ... 

Menggunakan * operator dari @aleksey-bykov komentar:

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

@streamich Menggandakan jumlah bit tidak sesuai dengan perkalian dengan dua, ini lebih seperti eksponen dengan 2 sebagai eksponen. Itu masih tidak benar, karena Anda tidak boleh menaikkan batas atas, tetapi angka yang dapat dikodekan tetap dihitung. Secara keseluruhan, itu bukan strategi definisi yang baik.

@streamich , beberapa komentar:

  • Menggunakan istilah char , word , dll. dapat membingungkan karena orang-orang dari bahasa lain mungkin tidak menyadari perbedaan antara definisi statis dan perilaku runtime.
  • Sintaks yang Anda usulkan tidak memperhitungkan batas bawah -- bagaimana jika bukan nol?
  • Saya akan berhati-hati tentang mengkooptasi operator eksponensial untuk digunakan dalam konteks ambient/type, karena sudah ditambahkan ke ES2016.

mari kita selesaikan turing sistem tipe dan nikmati masalah penghentian saat kita mencapai Ctrl + Shift + B

@aleksey-bykov pasti Anda ingat masalah bagus ini

@aluanhaddad

satuan-satuan itu [satuan waktu] berada pada interval yang cukup terbatas sehingga praktis dan sangat sulit untuk menuliskannya dengan tangan.

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

Sekarang, lakukan itu dengan milidetik :wink:

Apakah masalah ini sudah mati?

@Palid itu ditandai dengan [Needs Proposal] jadi saya ragu itu.

Meskipun diskusinya menyenangkan, kebanyakan dari kita gagal memberikan kasus penggunaan dunia nyata yang menarik.

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

__GUNAKAN KASUS:__

  1. Anda dapat menentukan tipe int yang tepat sehingga subset TypeScript dapat dikompilasi ke WebAssembly atau target lainnya.

  2. Kasus penggunaan lain adalah UInt8Array , Int8Array , Uint16Array , dll., ketika Anda membaca atau menulis data dari TypeScript tersebut dapat memeriksa kesalahan.

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

uint8[0] = 0xFFFFFFFF; // TSError: Number too big!
  1. Saya telah menyebutkan beberapa kasus penggunaan di OP saya.

  2. Jika Anda menerapkan ini, komunitas TypeScript akan menghasilkan jutaan kasus penggunaan.

Saya punya kasus penggunaan yang sangat lucu, yang sebenarnya mungkin lebih dekat dengan hal lain yang diusulkan di sini.
API mengambil bilangan bulat antara beberapa rentang (dalam kasus saya 5-30) dan kami perlu menerapkan SDK untuk itu.
Menulis secara manual di bawahnya membosankan (meskipun bisa agak otomatis) untuk 25nilai, tapi bagaimana dengan ratusan atau ribuan?
type X = 5 | 6 | 7 | 8 | 9 | 10 ... | 30

@Palid Itu kasus terbaik dan paling mudah yang pernah saya lihat untuk fitur ini.

Jika rentang seperti 5..30 didefinisikan sebagai gula sintaksis untuk 5 | 6 | 7 ... | 30 (atau berfungsi identik dengannya), saya yakin ini akan menjadi kemenangan yang mudah. Di sini itu akan berdiri untuk rentang bilangan bulat diskrit.

Mungkin rentang kontinu (sebagai lawan dari diskrit) dapat dinotasikan dengan menggunakan angka dengan periode -- 5.0..30.0 .

Saya sebenarnya mempertimbangkan untuk melihat TypeScript untuk menerapkan perlindungan pengetikan untuk
https://www.npmjs.com/package/memory-efficient-object

Memiliki pengetikan rentang akan memudahkan untuk menemukan potensi memori yang meluap pada waktu pemeriksaan tipe

Sepertinya kami tidak benar-benar membutuhkan perluasan serikat tetapi lebih seperti definisi rentang inklusif/eksklusif yang hanya membandingkan rentang

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

Ini akan sangat berguna saat mengendalikan motor (menggunakan Johnny-Five, misalnya), di mana kecepatannya berkisar antara 0-255.

Kasus penggunaan lain: Saya menerapkan program menggambar berbasis Kanvas menggunakan TypeScript dan ingin melakukan pemeriksaan tipe pada opacity (yang harus berupa angka antara 0,0 dan 1,0).

Hanya berpikir... untuk mengimplementasikan ini dengan benar, Anda harus benar-benar bekerja keras:

  • mendukung fungsi penjaga tipe runtime
  • ketik penyempitan untuk kondisi seperti x <= 10
  • dukungan untuk rentang string | number (karena x == 5 adalah true untuk x === "5" ) selain rentang angka saja
  • bahkan mungkin mendukung tipe int (yang akan menjadi subtipe dari number ), dan dukungan untuk int -only dan rentang string | int . Juga ketik penyempitan untuk ekspresi seperti x|0

Ide bagus, tapi itu akan banyak dibahas!

Mungkin kita tidak membutuhkan penjaga tipe runtime. Alih-alih, kita bisa membuat penjaga waktu kompilasi sepenuhnya

Ini akan membutuhkan prediksi cabang yang rumit sebagai gantinya

Memperkirakan

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;
}

Kasus penggunaan saya:

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[] },
  // ...
}

Akan menyenangkan untuk dapat membatasi kunci dalam opsi statusCode sedemikian rupa sehingga pada waktu kompilasi dapat ditentukan apakah kode status sesuai dengan kode sukses atau kesalahan:

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

IMO ini harus dibatasi untuk float dan integer dan tidak boleh diimplementasikan sebagai gabungan, melainkan tipe rentang baru. Kemudian pemeriksaan ketik akan sesederhana:

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

Kita mungkin bisa mengambil lembaran dari buku Ruby dan menggunakan .. untuk rentang inklusif ( [start, stop] ) dan ... untuk rentang non-inklusif ( [start, stop) ).

Kasus penggunaan lain adalah mengetikkan cek catatan basis data Anda:

Kasus penggunaan lainnya adalah mengetik nilai Lat/Lon.

Ini dapat dicakup oleh mekanisme validasi umum #8665 (tidak yakin mengapa ditutup sebagai duplikat):

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;

Atau diambil dengan #4639, dan dengan asumsi tipe integer yang tidak ditandatangani didefinisikan sebagai uint :

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

Kasus penggunaan saya adalah koordinat global. Saya ingin mengetik garis lintang dan bujur sehingga hanya berada dalam rentang tertentu (-90 hingga 90 dan -180 hingga 180).

Sunting: lat dan long memiliki rentang negatif

Kasus penggunaan saya adalah implementasi array dengan ukuran tetap di mana ukuran adalah parameter.

Misalnya, saya ingin mendefinisikan tipe untuk array string dengan panjang 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

Dengan versi TS saat ini, dimungkinkan untuk mendefinisikan sesuatu yang sangat dekat dengan "spek" di atas. Masalah utama adalah akses anggota: misalnya d[1] = 'b1' mengembalikan kesalahan jenis meskipun itu benar. Untuk menghindari kesalahan, daftar semua indeks legal harus dikompilasi secara manual dalam definisi FixedSizeArray , yang membosankan.

Jika kita memiliki operator range mirip dengan operator keyof , definisi tipe berikut akan menyelesaikan masalah.

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

Di mana range(N) adalah jalan pintas untuk range(0,N) .

Diberikan literal numerik (alami) M, N dengan M < N,

type r = range(M, N); 

setara dengan

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

Apa yang mungkin lebih umum adalah jika kita dapat memiliki kemampuan untuk mendefinisikan lambda predikat dan menggunakannya sebagai tipe. Contoh:

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

Seorang pro akan menjadi kompleksitas sintaksis yang rendah karena Lambdas sudah tersedia yang kita butuhkan hanyalah kemampuan untuk menggunakannya sebagai tipe, pemeriksaan tipe adalah khusus TypeScript (dengan mengingat filosofi "superset ke Javascript")
Sebuah kontra adalah bahwa kompleksitas predikat akan menentukan kinerja perkakas untuk memberikan umpan balik.

Memastikan angka/karakter milik progresi aritmatika bisa menjadi kasus penggunaan umum yang baik:

// 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
  }
}

ini akan memungkinkan kami untuk melakukan pemeriksaan tipe seperti:
predikat milikToMyAP = milikToAP(1,1, 10)

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

Ini dapat diperluas ke karakter juga.

@Kasahs Sesuatu yang mirip dengan ini telah diusulkan di #8665.

Melempar banyak saya dengan kasus penggunaan "mencerminkan API". Titik akhir REST Saya sedang menulis fungsi pembungkus untuk mengambil bilangan bulat dalam kisaran 1..1000 sebagai argumen. Ini menghasilkan kesalahan jika nomor tidak memenuhi kendala itu.

Jadi saya sedang menulis proposal untuk rentang numerik dan saya telah menemukan masalah ini yang saya tidak yakin bagaimana menanganinya, jadi saya membuangnya untuk dipertimbangkan.

Karena kompiler TypeScript ditulis dalam TypeScript, dimungkinkan untuk memanfaatkan properti operator numerik untuk mengubah tipe rentang.

// 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.

Ini bagus ketika menugaskan ke variabel baru, tetapi bagaimana dengan mutasi?

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).

Kesalahan ini secara teknis benar, tetapi dalam praktiknya akan sangat menjengkelkan untuk ditangani. Jika kita ingin mengubah variabel yang dikembalikan oleh fungsi dengan tipe pengembalian rentang, kita harus selalu menambahkan pernyataan tipe.

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.

Dan jika kita mengabaikannya, kita mendapatkan ketidaksehatan berikut:

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.

Perbaikan untuk ini adalah kemampuan untuk mengubah tipe variabel setelah dideklarasikan, jadi contoh 2 hanya akan mengubah tipe x menjadi 2..12; tetapi insting pertama saya adalah itu akan memperkenalkan terlalu banyak overhead di kompiler dan membingungkan pengguna.

Dan bagaimana dengan fungsi dan obat generik yang ditentukan pengguna?

// 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);

Adakah pemikiran tentang bagaimana menangani hal di atas?

@JakeTunaley

bagaimana dengan mutasi?

Mengapa mutasi harus berbeda dari penugasan lainnya?

Ini adalah usaha saya yang sederhana dalam sebuah proposal. Proposal ini juga meminta agar jenis lain ditambahkan.

  • Infinity jenis

    • Hanya memiliki nilai Infinity

  • -Infinity jenis

    • Hanya memiliki nilai -Infinity

  • NaN jenis

    • Hanya memiliki nilai NaN

  • double jenis

    • Semua nilai number dalam rentang [-Number.MAX_VALUE, Number.MAX_VALUE] atau, [-1.7976931348623157e+308, 1.7976931348623157e+308]

  • number hanya Infinity|-Infinity|NaN|double
  • int jenis

    • Sub-jenis double

    • Semua nilai number x dalam rentang [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER] atau [-9007199254740991, 9007199254740991] dan Math.floor(x) === x

    • Jadi, 3 dan 3.0 akan bernilai tipe int

  • Jenis "harfiah terbatas"

    • Contohnya adalah 1 , 3.141 , 45

    • Mungkin sub-tipe double atau int

  • Jenis "GtEq" dilambangkan (>= x)

    • Di mana x adalah literal hingga, Infinity , atau -Infinity

  • Jenis "LtEq" dilambangkan (<= x)

    • Di mana x adalah literal hingga, Infinity , atau -Infinity

  • Jenis "Gt" dilambangkan (> x)

    • Di mana x adalah literal hingga, Infinity , atau -Infinity

  • Jenis "Lt" dilambangkan (< x)

    • Di mana x adalah literal hingga, Infinity , atau -Infinity


jenis GtEq; (>= x)

  • (>= Infinity) = Infinity
  • (>= -Infinity) = -Infinity|double|Infinity
  • Infinity adalah subtipe dari (>= [finite-literal])
  • (>= [finite-literal]) adalah subtipe dari double|Infinity
  • (>= NaN) = never
  • (>= int) = (>= -9007199254740991)
  • (>= double) = (>= -1.7976931348623157e+308)
  • (>= number) = number

tipe gt; (> x)

  • (> Infinity) = never
  • (> -Infinity) = double|Infinity
  • Infinity adalah subtipe dari (> [finite-literal])
  • (> [finite-literal]) adalah subtipe dari double|Infinity
  • (> NaN) = never
  • (> int) = (> -9007199254740991)
  • (> double) = (> -1.7976931348623157e+308)
  • (> number) = number

jenis LtEq; (<= x)

  • (<= Infinity) = -Infinity|double|Infinity
  • (<= -Infinity) = -Infinity
  • -Infinity adalah subtipe dari (<= [finite-literal])
  • (<= [finite-literal]) adalah subtipe dari -Infinity|double
  • (<= NaN) = never
  • (<= int) = (<= 9007199254740991)
  • (<= double) = (<= 1.7976931348623157e+308)
  • (<= number) = number

tipe Lt; (< x)

  • (< Infinity) = -Infinity|double
  • (< -Infinity) = never
  • -Infinity adalah subtipe dari (< [finite-literal])
  • (< [finite-literal]) adalah subtipe dari -Infinity|double
  • (< NaN) = never
  • (< int) = (< 9007199254740991)
  • (< double) = (< 1.7976931348623157e+308)
  • (< number) = number

Jenis jangkauan

Perhatikan bahwa sementara kita dapat menulis hal-hal seperti (>= Infinity) , (> number) , dll.,
tipe yang dihasilkan bukan tipe range; mereka hanya alias untuk tipe lain.

Jenis jangkauan adalah salah satu dari,

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

Kami mengizinkan sintaks seperti (> number) dan sejenisnya untuk digunakan dalam obat generik.


Jenis serikat GtEq/Gt

Saat mengambil gabungan dua tipe GtEq/Gt, tipe dengan nilai "lebih" adalah hasilnya,

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

    • Jika [finite-literal-A] >= [finite-literal-B] , maka hasilnya adalah (>= [finite-literal-B])

    • Jika tidak, hasilnya adalah (>= [finite-literal-A])

    • misalnya (>= 3) | (>= 5.5) = (>= 3) karena (>= 3) adalah tipe super dari (>= 5.5)

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

    • Jika [finite-literal-A] == [finite-literal-B] , maka hasilnya adalah (>= [finite-literal-A])

    • Jika [finite-literal-A] > [finite-literal-B] , maka hasilnya adalah (> [finite-literal-B])

    • Jika tidak, hasilnya adalah (>= [finite-literal-A])

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

    • Jika [finite-literal-A] >= [finite-literal-B] , maka hasilnya adalah (> [finite-literal-B])

    • Jika tidak, hasilnya adalah (> [finite-literal-A])

    • misalnya (> 3) | (> 5.5) = (> 3) karena (> 3) adalah tipe super dari (> 5.5)

Juga,

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

    • misalnya (> 4|3) = (> 4) | (> 3) = (> 3)

    • misalnya (> number|3) = (> number) | (> 3) = number | (> 3) = number

Jenis Union LtEq / Lt

Saat mengambil gabungan dua tipe LtEq/Lt, tipe dengan nilai "lebih" adalah hasilnya,

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

    • Jika [finite-literal-A] <= [finite-literal-B] , maka hasilnya adalah (<= [finite-literal-B])

    • Jika tidak, hasilnya adalah (<= [finite-literal-A])

    • misalnya (<= 3) | (<= 5.5) = (<= 5.5) karena (<= 5.5) adalah tipe super dari (<= 3)

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

    • Jika [finite-literal-A] == [finite-literal-B] , maka hasilnya adalah (<= [finite-literal-A])

    • Jika [finite-literal-A] < [finite-literal-B] , maka hasilnya adalah (< [finite-literal-B])

    • Jika tidak, hasilnya adalah (<= [finite-literal-A])

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

    • Jika [finite-literal-A] <= [finite-literal-B] , maka hasilnya adalah (< [finite-literal-B])

    • Jika tidak, hasilnya adalah (< [finite-literal-A])

    • misalnya (< 3) | (< 5.5) = (< 5.5) karena (< 5.5) adalah tipe super dari (< 3)

Juga,

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

    • misalnya (< 4|3) = (< 4) | (< 3) = (< 4)

    • misalnya (< number|3) = (< number) | (< 3) = number | (< 3) = number


Jenis persimpangan GtEq/Gt

Saat mengambil persimpangan dua tipe GtEq/Gt, tipe dengan nilai "lebih sedikit" adalah hasilnya,

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

    • Jika [finite-literal-A] >= [finite-literal-B] , maka hasilnya adalah (>= [finite-literal-A])

    • Jika tidak, hasilnya adalah (>= [finite-literal-B])

    • misalnya (>= 3) & (>= 5.5) = (>= 5.5) karena (>= 5.5) adalah subtipe dari (>= 3)

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

    • Jika [finite-literal-A] == [finite-literal-B] , maka hasilnya adalah (> [finite-literal-B])

    • Jika [finite-literal-A] > [finite-literal-B] , maka hasilnya adalah (>= [finite-literal-A])

    • Jika tidak, hasilnya adalah (> [finite-literal-B])

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

    • Jika [finite-literal-A] >= [finite-literal-B] , maka hasilnya adalah (> [finite-literal-A])

    • Jika tidak, hasilnya adalah (> [finite-literal-B])

    • misalnya (> 3) & (> 5.5) = (> 5.5) karena (> 5.5) adalah subtipe dari (> 3)

Jenis persimpangan LtEq/Lt

Saat mengambil persimpangan dua tipe LtEq/Lt, tipe dengan nilai "lebih sedikit" adalah hasilnya,

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

    • Jika [finite-literal-A] <= [finite-literal-B] , maka hasilnya adalah (<= [finite-literal-A])

    • Jika tidak, hasilnya adalah (<= [finite-literal-B])

    • misalnya (<= 3) & (<= 5.5) = (<= 3) karena (<= 3) adalah subtipe dari (<= 5.5)

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

    • Jika [finite-literal-A] == [finite-literal-B] , maka hasilnya adalah (< [finite-literal-B])

    • Jika [finite-literal-A] < [finite-literal-B] , maka hasilnya adalah (<= [finite-literal-A])

    • Jika tidak, hasilnya adalah (< [finite-literal-B])

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

    • Jika [finite-literal-A] <= [finite-literal-B] , maka hasilnya adalah (< [finite-literal-A])

    • Jika tidak, hasilnya adalah (< [finite-literal-B])

    • misalnya (< 3) & (< 5.5) = (< 3) karena (< 3) adalah subtipe dari (< 5.5)


Gunakan Kasus

  • Untuk memastikan integer secara statis dapat masuk ke dalam tipe data MySQL UNSIGNED INT ,

    //TODO Propose numeric and range sum/subtraction/multiplication/division/mod/exponentiation types?
    function insertToDb (x : int & (>= 0) & (<= 4294967295)) {
      //Insert to database
    }
    
  • Untuk memastikan string memiliki panjang tertentu secara statis,

    function foo (s : string & { length : int & (>= 1) & (<= 255) }) {
      //Do something with this non-empty string that has up to 255 characters
    }
    
  • Untuk memastikan objek seperti array secara statis memiliki nilai length ,

    function foo (arr : { length : int & (>= 0) }) {
      //Do something with the array-like object
    }
    
  • Untuk memastikan secara statis kita hanya diberikan bilangan berhingga,

    function foo (x : double) {
      //`x` is NOT NaN|Infinity|-Infinity
    }
    
  • Untuk memastikan secara statis ada indeks array?

    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
    }
    

Saya ingin mengusulkan jenis penambahan/pengurangan/perkalian/pembagian/mod/eksponensial numerik dan rentang tetapi tampaknya di luar cakupan masalah ini.

[Sunting]
Anda dapat mengganti nama double dan menyebutnya float tetapi saya hanya berpikir bahwa double lebih akurat menyatakan bahwa ini adalah angka floating point presisi ganda.

[Sunting]

Mengubah beberapa jenis menjadi never .

Mungkinkah kompiler melakukan analisis aliran?

Misalkan ada fungsi ini

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
}

satu lagi kasus penggunaan: saya memiliki input angka ke fungsi yang mewakili persentase. saya ingin membatasi nilai menjadi antara 0 & 1.

Saya baru saja menjalankan [...Array(256)].map((_,i) => i).join("|") untuk membuat definisi tipe saya yang paling jelek

Bilangan bulat nonnegatif dan bilangan kecil dimungkinkan:

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

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

Dengan "angka kecil" maksud Anda tanpa menggunakan tahap 3 BigInt ?

@Mouvedia Angka yang sesuai dengan batas rekursi kompiler

Mungkin menggunakan .. untuk bilangan bulat dan ... untuk pelampung.

Saya akan mengatakan bahwa .. harus berarti rentang inklusif dan ... eksklusif. Sama seperti di misalnya Ruby. Lihat http://rubylearning.com/satishtalim/ruby_ranges.html

Saya bukan penggemar menggunakan satu periode untuk membedakan antara rentang inklusif dan eksklusif

Bisakah saya melompat? Saya pikir fitur ini dilihat sebagai peningkatan tunggal untuk spesifikasi tipe bukanlah cara terbaik untuk pergi ke sini.

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

Inilah cara Anda menentukan rentang numerik di banyak platform 4gl — khususnya yang berorientasi matriks.

Anda melakukannya dengan cara ini, karena rentang yang seragam secara konvensional merupakan salah satu pendekatan pemodelan terbaik.

Jadi rentang berurutan adalah ini:

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

Dan jika hanya bilangan bulat:

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

// OR

type integers = number.integers<1:10>

Jika kita ingin bermain-main dengan sintaks:

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

Jika itu tidak ramah tokenizer, ini bukan prioritas pemblokiran yang tepat untuk fokus di sini menurut saya. Saya menunjukkan ini dengan harapan tidak melihat satu solusi membatasi kita untuk mencapai solusi berikutnya.

sunting : Apa yang gagal saya perhitungkan adalah aspek inklusivitas — dan di sini kita harus bertanya-tanya apakah kita melakukan uji tuntas yang cukup untuk memahami mengapa itu bukan masalah ketika orang memecahkan begitu banyak masalah dengan mengandalkan rentang seragam yang secara implisit mencakup setiap angka kecuali jika kenaikan tidak benar-benar sejajar dengan akhir rentang.

Menarik bahwa Anda dapat menentukan ukuran "langkah" dalam jenis rentang.

Di luar rentang "titik mengambang" (tidak ada ukuran langkah tetap) dan "bilangan bulat" (ukuran langkah 1, dimulai dari bilangan bulat), saya belum pernah menemukan kasus penggunaan kehidupan nyata untuk rentang dengan ukuran langkah lain.

Jadi, menarik untuk mendengarnya menjadi sesuatu di tempat lain. Sekarang, saya perlu belajar tentang 4gl karena saya belum pernah mendengarnya sebelumnya.


Mampu mendefinisikan interval setengah terbuka berguna untuk objek seperti array. Sesuatu seperti,

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

Jika kami hanya memiliki rentang inklusif, kami membutuhkan (<= LengthT - 1) tetapi itu kurang elegan

Bisakah saya melompat? Saya pikir fitur ini dilihat sebagai peningkatan tunggal untuk spesifikasi tipe bukanlah cara terbaik untuk pergi ke sini.

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

Inilah cara Anda menentukan rentang numerik di banyak platform 4gl — khususnya yang berorientasi matriks.

Anda melakukannya dengan cara ini, karena rentang yang seragam secara konvensional merupakan salah satu pendekatan pemodelan terbaik.

Jadi rentang berurutan adalah ini:

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

Dan jika hanya bilangan bulat:

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

// OR

type integers = number.integers<1:10>

Jika kita ingin bermain-main dengan sintaks:

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

Jika itu tidak ramah tokenizer, ini bukan prioritas pemblokiran yang tepat untuk fokus di sini menurut saya. Saya menunjukkan ini dengan harapan tidak melihat satu solusi membatasi kita untuk mencapai solusi berikutnya.

sunting : Apa yang gagal saya perhitungkan adalah aspek inklusivitas — dan di sini kita harus bertanya-tanya apakah kita melakukan uji tuntas yang cukup untuk memahami mengapa itu bukan masalah ketika orang memecahkan begitu banyak masalah dengan mengandalkan rentang seragam yang secara implisit mencakup setiap angka kecuali jika kenaikan tidak benar-benar sejajar dengan akhir rentang.

Hmm sepertinya cara menanganinya di Haskell. Saya pikir ini bagus. Generator akan memungkinkan evaluasi malas juga.

Bukannya Anda tidak bisa melakukan evaluasi malas dengan sintaks lain =x

Menarik bahwa Anda dapat menentukan ukuran "langkah" dalam jenis rentang.

Saya menganggap rentang sebagai awal, akhir, dan kenaikan. Jadi jika kenaikan dibulatkan ke akhir yang tepat maka itu sudah termasuk itu. Indeks dan panjang array sebagai fungsi rentang dapat berupa array 0:1:9 memiliki 10 langkah indeks (panjang). Jadi kisaran di sini dapat dengan mudah menjadi integer & 0:9 untuk sistem tipe yang dapat lebih mudah disimpulkan dari menggabungkan dua ekspresi tersebut.

4GL benar-benar label generik, bagi saya itu sebagian besar MatLab

Maksud saya adalah bahwa hanya memiliki rentang inklusif akan membuat menggunakannya dalam obat generik lebih sulit (kecuali jika Anda menerapkan operasi matematika tingkat tipe hack-y).

Karena, alih-alih hanya memiliki "panjang" sebagai parameter tipe, Anda sekarang membutuhkan panjang dan indeks maks. Dan indeks maks harus sama dengan panjang-1.

Dan, sekali lagi, Anda tidak dapat benar-benar memeriksa kasusnya kecuali Anda menerapkan operasi matematika level tipe hack-y itu

@AnyhowStep Saya sedang memikirkan cara terbaik untuk membingkai kekhawatiran Anda - tetapi mungkin ini membantu untuk mengklarifikasi terlebih dahulu:

Jika kita mengesampingkan inklusif-atau-tidak secara khusus berlaku untuk masalah n-1 apa pun (seperti skenario indeks/hitungan ini), dengan asumsi itu diselesaikan secara independen entah bagaimana (hanya menghibur pikiran), apakah ada skenario lain yang rentang numeriknya akan masih memerlukan sintaks yang lebih sedikit deklaratif dan/atau tidak konvensional untuk dijelaskan dengan benar?

Saya mengerti bahwa itu adalah 2 aspek yang terpisah, Anda memerlukan rentang numerik yang secara konvensional selaras dengan harapan domain itu dan Anda juga memerlukan predikat untuk jenis indeks/jumlah ... dll.

bilangan positif saja.

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

Dengan rentang inklusif saja,

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

Angka positif, tidak termasuk Infinity ,

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

Dengan rentang inklusif saja,

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

Juga, tidak terkait dengan diskusi saat ini tetapi inilah alasan lain saya sangat menginginkan tipe rentang numerik.

Banyak pekerjaan saya sehari-hari hanya menyatukan banyak sistem perangkat lunak yang berbeda. Dan banyak dari sistem ini memiliki persyaratan berbeda untuk data yang memiliki arti yang sama.

misalnya, (sistemA, nilai antara 1 dan 255), (sistemB, nilai antara 3 dan 73), dll.
misalnya (systemC. panjang string 7-88), (systemD, panjang string 9-99), (systemE, panjang string 2-101), dll.

Saat ini, saya perlu dengan hati-hati mendokumentasikan semua persyaratan terpisah ini dan memastikan data dari satu sistem dapat dipetakan dengan benar ke sistem lain. Jika tidak memetakan, saya perlu mencari solusi.

Saya hanya manusia biasa. Saya membuat kesalahan. Saya tidak menyadari bahwa data terkadang tidak dapat dipetakan. Pemeriksaan jangkauan gagal.

Dengan tipe rentang numerik, saya akhirnya bisa mendeklarasikan rentang yang diharapkan setiap sistem dan membiarkan kompiler melakukan pemeriksaan untuk saya.


Misalnya, saya baru saja mengalami situasi di mana API yang saya gunakan memiliki batas panjang string 10k untuk semua nilai string. Yah, saya tidak punya cara untuk memberi tahu TypeScript untuk memeriksa bahwa semua string yang masuk ke API adalah <= panjang string 10k.

Saya memiliki kesalahan run-time alih-alih kesalahan kompilasi yang bagus di mana TS bisa pergi,

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

@AnyhowStep Saya harap Anda dapat menghargai bahwa maksud saya hanyalah untuk memastikan bahwa jika sesuatu yang disebut "Rentang sebagai tipe Angka" hanya sesuai dengan harapan umum untuk pengguna yang lebih konvensional (yaitu seseorang yang pindah ke TS bertanya-tanya mengapa rentang tersebut menekankan inklusivitas pada interval)

Sejujurnya saya berpikir bahwa use case harus selalu mendorong fitur, dan berpikir bahwa masalah yang berkaitan dengan indeks dan panjang adalah masalah yang terkadang sangat kita lewatkan. Jadi saya hanya ingin mengatasi masalah itu tepat di bawah label yang benar - apakah itu tipe angka atau tipe yang diindeks? Saya tidak tahu jawabannya secara pasti, tetapi saya ragu untuk berpikir bahwa menyelesaikannya sebagai hal tipe angka tidak akan secara tidak sengaja menciptakan lebih banyak masalah bagi pengguna tipe angka yang tidak berbagi pemahaman yang sama tentang aspek-aspek ini.

Jadi di mana lagi memperbaiki masalah seperti itu masuk akal bagi semua orang adalah semua yang saya ingin tahu pada saat ini, pikiran?

Saya berurusan dengan API yang melewati array byte, jadi saya ingin mendefinisikan tipe byte:

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

Ini juga akan berguna saat bekerja dengan Uint8Array .

Jika Anda seperti saya dan Anda tidak sabar untuk mendapatkan jenis rentang yang sebenarnya dapat Anda gunakan saat ini, berikut adalah cuplikan kode jenis rentang yang sedang beraksi.

TL; DR,
Jenis rentang dapat berfungsi sekarang

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
}

Tempat bermain

Ini menggunakan tipe CompileError<> dari sini,
https://github.com/microsoft/TypeScript/issues/23689#issuecomment -512114782

Tipe AssertStringLengthLt<> adalah tempat keajaiban terjadi

Menggunakan penambahan tingkat tipe, Anda dapat memiliki, str512 + str512 dan mendapatkan str1024

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

Melihat solusi @AnyhowStep saya pikir saya memiliki solusi sementara yang lebih baik. Ingat jenis penjaga?:

/**
 * 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' });

Jadi lihat kode berikut:

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')
}

Itu hanya sebuah ide tetapi dengan penjaga tipe dimungkinkan untuk menggunakan rentang.

Masalahnya di sini adalah Anda tidak dapat melakukan ini,

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
}

Jenis pelindung saja bukanlah solusi yang memuaskan

Juga, contoh Anda bahkan tidak menggunakan pelindung tipe.


Contoh mainan hacky konyol saya di atas memungkinkan Anda menetapkan tipe rentang ke tipe rentang lain (melalui parameter fungsi), jika batasnya ada di dalam yang lain.


Juga, tipe tag, tipe nominal, dan objek nilai yang digunakan pada primitif biasanya merupakan tanda bahwa sistem tipe tidak cukup ekspresif.

Saya benci contoh mainan konyol saya yang menggunakan jenis tag karena sangat tidak ergonomis. Anda dapat merujuk ke komentar panjang ini untuk mengetahui lebih baik rentang menjadi primitif.

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

Anda dapat mencapai ini dengan versi TypeScript saat ini:

// 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

Saya mengikuti konvensi memiliki indeks awal yang inklusif dan indeks akhir yang eksklusif, tetapi Anda dapat menyesuaikannya dengan kebutuhan Anda.

Petunjuk arah vs kode dalam komentar.

Perhatikan bahwa angka-angka dalam petunjuk arahkan kursor diurutkan secara acak.

image

Sayangnya itu hanya berfungsi hingga sekitar 10 elemen :(

Sunting: tampaknya Enumerate hanya dapat menangani hingga 15 rekursi (0 - 14)

@Shinigami92
Dengan pendekatan ini, Enumerate menangani hingga 40.
Ini masih merupakan batasan, tetapi mungkin tidak terlalu keras dalam praktiknya.

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>;

Dan sekarang kebetulan diurutkan entah bagaimana secara ajaib ;).

Kasus penggunaan umum bagi saya menggunakan rentang seperti [1, 255] , [1, 2048] , [1, 4096] , [20, 80] , dll. Membuat tipe serikat besar dapat membuat TS panik/melambat . Tetapi solusi itu pasti berfungsi untuk rentang "lebih kecil"

@AnyhowStep
Mengetahui bahwa jumlah rekursi adalah batas, kita harus menemukan cara untuk melakukan pembagian/perkalian bilangan dengan dua dalam operasi non-rekursif tunggal di dalam definisi tipe untuk mencapai rentang ini.

Performa masih menjadi masalah. Saya harus menyisihkan jenis serikat pekerja yang berguna dari aplikasi besar sebelumnya karena alasan ini - meskipun ini mungkin latihan yang menarik, itu jelas bukan solusi.

Masih belum ada solusi yang sempurna, kurasa?

Saya harap ini dapat dilakukan sebagai berikut secara umum (tetapi sulit).

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

(yaitu pernyataan/fungsi javascript normal dapat digunakan di sini, kecuali variabel diganti dengan tipe)

Dalam pemahaman saya, bahasa modern = logika normal + logika meta, di mana logika meta = pemeriksaan kode + pembuatan kode. Banyak karya hanya mencoba menggabungkan sintaksis meta logika dengan cara yang elegan.

Mungkin kita entah bagaimana bisa menganggap tipe rentang bukan semacam gula, tetapi menjadi konsep inti?

// 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

Untuk menyelesaikan ini, kita perlu mendeklarasikan perilaku pada beberapa kasus

  1. Range<T, T> === T menurut definisi
  2. Range<5, 1> === NaN menurut definisi
  3. Range<NaN, T> === Range<T, NaN> === NaN karena nilai seperti itu tidak ada
  4. Range<1 | 2, 5> === Range<2, 5> kemungkinan angka yang lebih besar karena parameter tipe pertama membatasi rentang menjadi lebih pendek
  5. Range<1, 4 | 5> === Range<1, 4> kemungkinan angka yang lebih rendah karena parameter tipe kedua membatasi rentang menjadi lebih pendek
  6. 1.5 extends Range<1, 2> harus benar menurut definisi
  7. Range<Range<A, B>, C> === NaN extends Range<A, B> ? NaN : Range<B, C> mengikuti dari 5 dan 3
  8. Range<A, Range<B, C>> === NaN extends Range<B, C> ? NaN : Range<A, B> mengikuti dari 6 dan 3

Dan sedikit tentang Rentang di dalam Rentang:

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

Subtipe rentang bahkan dapat didefinisikan secara umum, sebagai fungsi perbandingan tingkat tipe (a: T, b: T) => '<' | '=' | '>' .

... apakah ada proposal untuk menggunakan fungsi JS biasa di level tipe?

Tidak ada proposal, per sey, tetapi saya telah menulis prototipe cepat sebelumnya . Sayangnya, ada kekhawatiran besar atas kinerja IDE dan sanitasi input.

Masalah @yudinns adalah Rentang ada setidaknya pada nama ini, jadi mungkin membingungkan, lihat: https://developer.mozilla.org/en-US/docs/Web/API/range juga saya kehilangan fungsionalitas di mana saya dapat mendefinisikan deret bilangan seperti <1, n+2> - langkah 2 dimulai dari 1 atau lebih persamaan yang rumit.

Proposal TC39 ini mungkin akan mencapai tahap 4 sebelum diimplementasikan.
Tiketnya berumur 3 tahun.

Saran: Jenis string yang divalidasi Regex: https://github.com/Microsoft/TypeScript/issues/6579

(pertanyaan SO terkait: https://stackoverflow.com/questions/3895478/does-javascript-have-a-method-like-range-to-generate-a-range-within-the-supp)

Ini juga akan keren jika Anda bisa melakukan sesuatu seperti number in range misalnya if 1 in 1..2 atau inklusif if 1 in 1..=2 rust menanganinya dengan baik https://doc.rust-lang.org/reference/ ekspresi/range-expr.html . Itu akan menghemat banyak ruang

Bukankah python dan rust sudah memiliki fitur rentang yang diketik?
Bisakah kita mengimplementasikan kembali solusi yang ada dalam bahasa lain alih-alih menemukan kembali roda atau membuatnya lebih umum dengan string, float dll sementara tujuan utama dan usecase terbesar adalah rentang angka. Kami dapat menambahkan lebih banyak nanti saat dibutuhkan

Saya telah mengalami ini lebih dari satu kali dan datang ke sini mencari cara untuk menentukan jenis yang merupakan pecahan antara 0...1 - ini sedang dibahas di https://news.ycombinator.com/item?id= 24362658#24372935 karena tidak ada kata yang tepat dalam bahasa Inggris untuk mendefinisikan jenis nilai ini. Jika TypeScript dapat membantu di sini, itu akan sangat baik tetapi mungkin sangat sulit untuk diterapkan pada level tipe.

@andrewphillipo Saya melihat itu sedang dibahas juga, dan belajar tentang istilah Interval Unit dari salah satu jawaban di pertukaran tumpukan, yang tampaknya merupakan cara yang benar untuk merujuk ke rentang spesifik itu dalam konteks umum.

Jika rentang sebagai tipe pernah diimplementasikan dalam TypeScript, mungkin UnitInterval dapat didefinisikan sebagai tipe global, untuk mendorong beberapa nomenklatur umum di sekitar sesuatu yang sering kita gunakan dalam pemrograman.

Ya UnitInterval adalah nama yang benar untuk rentang 0...1 jadi itu benar untuk tipenya! Masih tidak benar untuk penamaan nomor sehingga akan sangat baik jika ini tersedia untuk menggambarkan kode kami lebih tepat menggunakan tipe seperti itu - bagaimana cara kerjanya di bawah tenda di sistem tipe Rust - apakah itu hanya penjaga atau?

Jika CantorSpace tidak terlalu berlebihan, saya pikir itu akan menjadi definisi yang adil dari "rentang" dari "semua" bilangan real antara [0, 1] seperti yang dipahami oleh komputer. Menetapkan ini ke nilai pada waktu kompilasi tidak dapat disimpulkan oleh batas bawah dan atas javascript Math.floor atau Math.ceil sejak Math.ceil(0) === 0 , dan Math.floor(1) === 1 yang malang.

Jika kumpulan semua angka (0, 1) digunakan sebagai gantinya, itu akan berfungsi menggunakan contoh di atas, tetapi itu agak buruk untuk mengecualikan nilai yang dirujuk sebagai persentase dalam bahasa sehari-hari. Jika memungkinkan, meminta kompiler menyertakan batasan entah bagaimana akan menyenangkan, mungkin melalui pemeriksaan === ketat terhadap kasus tepi 0 dan 1.

atau Mungkin menggunakan 1~~3 untuk bilangan bulat dan 0.1~0.5 untuk float ?

~ sudah diambil oleh operator NOT bitwise unary.

Mengapa kita perlu berbicara tentang pelampung sialan itu, masalah yang kita miliki adalah dengan bilangan bulat. 99,99% adalah masalah bilangan bulat 0,001% adalah masalah float.
Tidak ada solusi umum yang mungkin dengan mudah, jadi mari kita gunakan rentang angka 0..10 seperti yang terlihat di sebagian besar bahasa pemrograman, seperti yang sudah diterapkan beberapa tahun lalu.

Berhenti bicara pelampung

Mungkin kita bisa membagi tagihan dan memperluas proposal ini untuk kasus int:
https://Gist.github.com/rbuckton/5fd81582fdf86a34b45bae82d842304c

Dan untuk float, saat ini saya sedang mengerjakan proposal desain yang berbeda berdasarkan beberapa ide dalam masalah ini (terutama @AnyhowStep; pada dasarnya kemampuan untuk memiliki Open-Interval-Type).

Tetap sederhana dengan
x..y untuk bilangan bulat.
0.1..2 akan memberikan kesalahan
1..2.1 juga akan memberikan kesalahan
Dan setiap non bilangan bulat yang terdeteksi.
Heck salin logika implementasi dari
https://en.m.wikibooks.org/wiki/Ada_Programming/Types/range
Atau
https://kotlinlang.org/docs/reference/ranges.html
Atau
https://doc.rust-lang.org/reference/expressions/range-expr.html
dan menyebutnya sehari.

Tidak perlu menemukan kembali roda
Tidak perlu memperhitungkan float
Konsistensi lintas bahasa
Diuji dan menyalin kode stabil dari sumber yang ada

Ini sepadan dengan upaya untuk memecahkan float, karena pemeriksaan tipe berbasis "int" sepele untuk angka <1000 dengan mengkodekan nilai tipe sebagai gabungan dari setiap bilangan bulat tetap 0..1000, jadi katakan bahwa "tidak perlu" untuk "kembali -menemukan roda" sedikit ironis. Mengapa tidak menggunakan apa yang sudah berfungsi untuk kasus penggunaan sederhana?

Saya lebih suka melihat beberapa ruangan untuk memungkinkan definisi tipe yang memahami hal-hal seperti bilangan irasional. Kemampuan untuk mendeteksi invarian pada waktu kompilasi saat bekerja dengan radian akan menjadi kasus penggunaan yang menarik. Mendukung sebagai argumen untuk batas nilai rentang adalah target yang baik untuk mengklaim bahwa fitur ini berfungsi sebagaimana dimaksud.

Perlu diingat bahwa tidak ada integer eksplisit dalam javascript. Semuanya hanya number , yang disimpan sebagai pelampung. Jika operator tipe rentang diimplementasikan, ia harus menangani nilai apa pun yang dapat ditangani oleh number .

@aMoniker tergantung pada definisi explicit : BigInt

Bahkan untuk bigint, ada kasus penggunaan yang tidak dapat diselesaikan serikat pekerja seperti memastikan bigint cocok dengan bigint yang ditandatangani/tidak ditandatangani MySQL

Bagaimana dengan membuat jenis rentang intrinsic untuk fitur ini?

lib.es5.d.ts (atau lib.es2020.bigint.d.ts untuk menyertakan dukungan untuk 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`)
 */
tanah pengguna
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>`

@RyanCavanaugh

Ini benar-benar membutuhkan beberapa kasus penggunaan yang menarik, karena penerapan serikat pekerja saat ini sama sekali tidak cocok untuk mewujudkan serikat pekerja sebesar ini. Sesuatu yang akan terjadi jika Anda menulis sesuatu seperti ini ...

Berikut ini akan menjadi tidak sehat tanpa tipe 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

Memiliki tipe berikut akan mencegah masalah ini:

type TUInt = 0..4294967295;

Kasus penggunaan lain untuk memiliki Int32 dan Int64 secara khusus adalah ketika orang mulai membuat anotasi kode mereka dengan itu ... akan membuka pintu untuk interoperabilitas yang lebih baik dengan bahasa lain ... hampir semua diketik secara statis bahasa memiliki tipe integer: Java, C#, C, Rust, F#, Go ... dll.

Jika saya ingin memanggil perpustakaan npm yang ditulis dalam TypeScript dari C# misalnya, ada perpustakaan yang mengambil definisi TypeScript dan membuat antarmuka untuk saya di C# tetapi masalahnya adalah tipe number adalah float yang tidak dapat digunakan untuk mengindeks array di C # tanpa casing ... dll.

Kasus penggunaan lainnya: lebih mudah untuk berpindah antar bahasa, optimalisasi kinerja ... dll

Perhatikan bahwa satu kasus lagi di mana ini akan berguna adalah interaksi dengan WebAssembly, yang memiliki jenis nomor terpisah yang sangat eksplisit (saya kira itu disebutkan secara singkat sebagai kasus penggunaan C #, tetapi ingin mengklarifikasi bahwa itu berlaku lebih luas dari itu).

UPD: Nvm, saya melihatnya disebutkan di https://github.com/Microsoft/TypeScript/issues/15480#issuecomment -365420315 yang Github "sangat" sembunyikan sebagai "item tersembunyi".

+1

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

weswigham picture weswigham  ·  3Komentar

MartynasZilinskas picture MartynasZilinskas  ·  3Komentar

kyasbal-1994 picture kyasbal-1994  ·  3Komentar

bgrieder picture bgrieder  ·  3Komentar

Antony-Jones picture Antony-Jones  ·  3Komentar