Typescript: Proposal: Jenis Variadik -- Berikan jenis khusus untuk fungsi variadik

Dibuat pada 29 Okt 2015  ·  265Komentar  ·  Sumber: microsoft/TypeScript

Jenis Variadik

Berikan Tipe Khusus untuk Fungsi Variadik

Proposal ini memungkinkan TypeScript memberikan jenis ke fungsi tingkat tinggi yang mengambil sejumlah parameter variabel.
Fungsi seperti ini termasuk concat , apply , curry , compose dan hampir semua dekorator yang membungkus fungsi.
Dalam Javascript, fungsi tingkat tinggi ini diharapkan menerima fungsi variadik sebagai argumen.
Dengan standar ES2015 dan ES2017, penggunaan ini akan menjadi lebih umum karena programmer mulai menggunakan argumen spread dan parameter istirahat untuk array dan objek.
Proposal ini membahas kasus penggunaan ini dengan satu strategi pengetikan yang sangat umum berdasarkan jenis tingkat tinggi.

Proposal ini seluruhnya atau sebagian akan membahas beberapa masalah, termasuk:

  1. #5331 -- Tuple sebagai tipe untuk istirahat ...argumen
  2. #4130 -- Kompilator salah melaporkan ketidakcocokan tanda tangan target parameter/panggilan saat menggunakan operator spread
  3. #4988 -- Tuples harus dapat diklon dengan Array.prototype.slice()
  4. #1773 -- Obat generik variadik?
  5. #3870 -- Tipe istirahat dalam generik untuk tipe persimpangan.
  6. #212 -- bind, call, dan apply tidak diketik (memerlukan tipe fungsi this #3694).
  7. #1024 -- Ketik ... parameter istirahat dengan obat generik

Saya akan memperbarui proposal ini di fork saya dari TypeScript-Handbook: sandersn/ TypeScript-Handbook@76f5a75868de3fb1ad4dbed5db437a8ab61a2698
Saya memiliki implementasi yang sedang berlangsung di sandersn/ TypeScript@f3c327aef22f6251532309ba046874133c32f4c7 yang saat ini memiliki bagian sederhana dari proposal yang diterapkan.
Ini menggantikan bagian 2 dari proposal saya sebelumnya, #5296.
Sunting: Menambahkan bagian tentang penugasan. Saya tidak lagi yakin bahwa itu benar-benar menggantikan #5296.

Pratinjau contoh dengan curry

curry untuk fungsi dengan dua argumen mudah ditulis dalam Javascript dan TypeScript:

function curry(f, a) {
    return b => f(a, b);
}

dan dalam TypeScript dengan anotasi tipe:

function curry<T, U, V>(f: (t: T, u: U) => V, a:T): (b:U) => V {
    return b => f(a, b);
}

Namun, versi variadic mudah ditulis dalam Javascript tetapi tidak dapat diberi tipe dalam TypeScript:

function curry(f, ...a) {
    return ...b => f(...a, ...b);
}

Berikut adalah contoh penggunaan jenis variadik dari proposal ini untuk mengetik curry :

function curry<...T,...U,V>(f: (...ts: [...T, ...U]) => V, ...as:...T): (...bs:...U) => V {
    return ...b => f(...a, ...b);
}

Sintaks untuk tipe tupel variadic yang saya gunakan di sini cocok dengan sintaks spread dan rest yang digunakan untuk nilai dalam Javascript.
Ini lebih mudah dipelajari tetapi mungkin mempersulit untuk membedakan anotasi tipe dari ekspresi nilai.
Demikian pula, sintaks untuk menggabungkan terlihat seperti konstruksi tupel, meskipun sebenarnya merupakan gabungan dari dua jenis tupel.

Sekarang mari kita lihat contoh panggilan ke curry :

function f(n: number, m: number, s: string, c: string): [number, number, string, string] {
    return [n,m,s,c];
}
let [n,m,s,c] = curry(f, 1, 2)('foo', 'x');
let [n,m,s,c] = curry(f, 1, 2, 'foo', 'x')();

Pada panggilan pertama,

V = [number, number, string, string]
...T = [number, number]
...U = [string, string]

Pada panggilan kedua,

V = [number, number, string, string]
...T = [number, number, string, string]
...U = []

Sintaksis

Sintaks dari variabel jenis variadic adalah ...T mana _T_ adalah pengidentifikasi yang dengan konvensi huruf besar tunggal, atau T diikuti oleh pengenal PascalCase .
Variabel jenis variadik dapat digunakan dalam sejumlah konteks sintaksis:

Jenis variadik dapat diikat di lokasi biasa untuk pengikatan parameter tipe, termasuk fungsi dan kelas:

function f<...T,...U>() {}
}
class C<...T> {
}

Dan mereka dapat direferensikan di lokasi anotasi jenis apa pun:

function makeTuple<...T>(ts:...T): ...T {
    return ts;
}
function f<...T,...U>(ts:...T): [...T,...U] {
    // note that U is constrained to [string,string] in this function
    let us: ...U = makeTuple('hello', 'world');
    return [...ts, ...us];
}

Variabel jenis variadik, seperti variabel jenis, cukup buram.
Mereka memiliki satu operasi, tidak seperti variabel tipe.
Mereka dapat digabungkan dengan jenis lain atau dengan tupel yang sebenarnya.
Sintaks yang digunakan untuk ini identik dengan sintaks penyebaran tupel, tetapi di lokasi anotasi tipe:

let t1: [...T,...U] = [...ts,...uProducer<...U>()];
let t2: [...T,string,string,...U,number] = [...ts,'foo','bar',...uProducer<...U>(),12];

Jenis tupel adalah contoh dari jenis variadik, sehingga mereka terus muncul di mana pun anotasi jenis sebelumnya diizinkan:

function f<...T>(ts:...T): [...T,string,string] { 
    // note the type of `us` could have been inferred here
    let us: [string,string] = makeTuple('hello', 'world');
    return [...ts, ...us];
}

let tuple: [number, string] = [1,'foo'];
f<[number,string]>(tuple);

Semantik

Variabel jenis variadik mewakili jenis tupel dengan panjang berapa pun.
Karena mewakili satu set tipe, kami menggunakan istilah 'jenis' untuk merujuknya, mengikuti penggunaannya dalam teori tipe.
Karena kumpulan tipe yang diwakilinya adalah tupel dengan panjang berapa pun, kami mengkualifikasikan 'jenis' dengan 'variadik'.

Oleh karena itu, mendeklarasikan variabel jenis tupel variadik memungkinkannya untuk mengambil jenis tupel _single_ apa pun.
Seperti variabel tipe, variabel jenis hanya dapat dideklarasikan sebagai parameter untuk fungsi, kelas, dll, yang kemudian memungkinkannya digunakan di dalam tubuh:

function f<...T>(): ...T {
    let a: ...T;
}

Memanggil fungsi dengan argumen yang diketik sebagai jenis variadik akan menetapkan jenis tupel tertentu ke jenis tersebut:

f([1,2,"foo"]);

Menetapkan tipe tuple ...T=[number,number,string] ...T . So in this application of f , let a:...T is instantiated as let a:[number,number,string] . However, because the type of a is not known when the function is written, the elements of the tuple cannot be referenced in the body of the function. Only creating a new tuple from a` diperbolehkan.
Misalnya, elemen baru dapat ditambahkan ke tupel:

function cons<H,...Tail>(head: H, tail: ...Tail): [H,...Tail] {
    return [head, ...tail];
}
let l: [number, string, string, boolean]; 
l = cons(1, cons("foo", ["baz", false]));

Seperti variabel tipe, variabel tipe variadik biasanya dapat disimpulkan.
Panggilan ke cons bisa saja dianotasi:

l = cons<number,[string,string,boolean]>(1, cons<string,[string,boolean]>("foo", ["baz", false]));

Misalnya, cons harus menyimpulkan dua variabel, tipe _H_ dan jenis _...Tail_.
Dalam panggilan terdalam, cons("foo", ["baz", false]) , H=string dan ...Tail=[string,boolean] .
Di panggilan terluar, H=number dan ...Tail=[string, string, boolean] .
Tipe yang ditetapkan ke _...Tail_ diperoleh dengan mengetik literal daftar sebagai tupel -- variabel dari tipe tupel juga dapat digunakan:

let tail: [number, boolean] = ["baz", false];
let l = cons(1, cons("foo", tail));

Selain itu, variabel jenis variadik dapat disimpulkan saat digabungkan dengan tipe:

function car<H,...Tail>(l: [H, ...Tail]): H {
    let [head, ...tail] = l;
    return head;
}
car([1, "foo", false]);

Di sini, tipe l disimpulkan sebagai [number, string, boolean] .
Kemudian H=number dan ...Tail=[string, boolean] .

Batasan pada inferensi tipe

Jenis gabungan tidak dapat disimpulkan karena pemeriksa tidak dapat menebak di mana batas antara dua jenis seharusnya:

function twoKinds<...T,...U>(total: [...T,string,...U]) {
}
twoKinds("an", "ambiguous", "call", "to", "twoKinds")

Pemeriksa tidak dapat memutuskan apakah akan menetapkan

  1. ...T = [string,string,string], ...U = [string]
  2. ...T = [string,string], ...U = [string,string]
  3. ...T = [string], ...U = [string,string,string]

Beberapa panggilan yang tidak ambigu adalah korban dari pembatasan ini:

twoKinds(1, "unambiguous", 12); // but still needs an annotation!

Solusinya adalah menambahkan anotasi tipe:

twoKinds<[string,string],[string,string]>("an", "ambiguous", "call", "to", "twoKinds");
twoKinds<[number],[number]>(1, "unambiguous", 12);

Ketergantungan yang tidak dapat dicentang antara argumen tipe dan badan fungsi dapat muncul, seperti pada rotate :

function rotate(l:[...T, ...U], n: number): [...U, ...T] {
    let first: ...T = l.slice(0, n);
    let rest: ...U = l.slice(n);
    return [...rest, ...first];
}
rotate<[boolean, boolean, string], [string, number]>([true, true, 'none', 12', 'some'], 3);

Fungsi ini dapat diketik, tetapi ada ketergantungan antara n dan jenis variabel: n === ...T.length harus benar agar jenisnya benar.
Saya tidak yakin apakah ini kode yang seharusnya diizinkan.

Semantik pada kelas dan antarmuka

Semantiknya sama pada kelas dan antarmuka.

TODO: Mungkin ada beberapa kerutan khusus kelas dalam semantik.

Penugasan antara tupel dan daftar parameter

Jenis Tuple dapat digunakan untuk memberikan tipe untuk mengistirahatkan argumen fungsi di dalam cakupannya:

function apply<...T,U>(ap: (...args:...T) => U, args: ...T): U {
    return ap(...args);
}
function f(a: number, b: string) => string {
    return b + a;
}
apply(f, [1, 'foo']);

Dalam contoh ini, daftar parameter f: (a: number, b:string) => string harus dapat ditetapkan ke tipe tupel yang dipakai untuk jenis ...T .
Jenis tupel yang disimpulkan adalah [number, string] , yang berarti bahwa (a: number, b: string) => string harus dapat ditetapkan ke (...args: [number, string]) => string .

Sebagai efek samping, pemanggilan fungsi akan dapat mengambil keuntungan dari penetapan ini dengan menyebarkan tupel ke parameter istirahat, bahkan jika fungsi tersebut tidak memiliki jenis tupel:

function g(a: number, ...b: [number, string]) {
    return a + b[0];
}
g(a, ...[12, 'foo']);

Jenis Tuple yang dihasilkan untuk parameter opsional dan istirahat

Karena tupel tidak dapat mewakili parameter opsional secara langsung, ketika suatu fungsi ditetapkan ke parameter fungsi yang diketik oleh jenis tupel, jenis tupel yang dihasilkan adalah gabungan dari jenis tupel.
Perhatikan jenis h setelah dikukus:

function curry<...T,...U,V>(cur: (...args:[...T,...U]) => V, ...ts:...T): (...us:...U) => V {
    return ...us => cur(...ts, ...us);
}
function h(a: number, b?:string): number {
}
let curried = curry(h, 12);
curried('foo'); // ok
curried(); // ok

Di sini ...T=([number] | [number, string]) , jadi curried: ...([number] | [number, string]) => number yang dapat dipanggil seperti yang Anda harapkan. Sayangnya, strategi ini tidak berfungsi untuk parameter istirahat. Ini baru saja diubah menjadi array:

function i(a: number, b?: string, ...c: boolean[]): number {
}
let curried = curry(i, 12);
curried('foo', [true, false]);
curried([true, false]);

Di sini, curried: ...([string, boolean[]] | [boolean[]]) => number .
Saya pikir ini dapat didukung jika ada kasus khusus untuk fungsi dengan parameter istirahat Tuple, di mana elemen terakhir dari Tuple adalah array.
Dalam hal ini pemanggilan fungsi akan memungkinkan argumen tambahan dari tipe yang benar untuk mencocokkan array.
Namun, itu tampaknya terlalu rumit untuk menjadi berharga.

Ekstensi ke bagian lain dari TypeScript

  1. TypeScript tidak mengizinkan pengguna untuk menulis tipe tuple kosong.
    Namun, proposal ini membutuhkan jenis variadik untuk dapat diikat ke tupel kosong.
    Jadi TypeScript perlu mendukung tupel kosong, meskipun hanya secara internal.

    Contoh

Sebagian besar contoh ini dimungkinkan sebagai fungsi argumen tetap dalam TypeScript saat ini, tetapi dengan proposal ini mereka dapat ditulis sebagai variadik.
Beberapa, seperti cons dan concat , dapat ditulis untuk array homogen dalam TypeScript saat ini tetapi sekarang dapat ditulis untuk tupel heterogen menggunakan jenis tuple.
Ini mengikuti praktik Javascript khas lebih dekat.

Kembalikan tipe gabungan

function cons<H,...T>(head: H, tail:...T): [H, ...T] {
    return [head, ...tail];
}
function concat<...T,...U>(first: ...T, ...second: ...U): [...T, ...U] {
    return [...first, ...second];
}
cons(1, ["foo", false]); // === [1, "foo", false]
concat(['a', true], 1, 'b'); // === ['a', true, 1, 'b']
concat(['a', true]); // === ['a', true, 1, 'b']

let start: [number,number] = [1,2]; // type annotation required here
cons(3, start); // == [3,1,2]

Jenis gabungan sebagai parameter

function car<H,...T>(l: [H,...T]): H {
    let [head, ...tail] = l;
    return head;
}
function cdr<H,...T>(l: [H,...T]): ...T {
    let [head, ...tail] = l;
    return ...tail;
}

cdr(["foo", 1, 2]); // => [1,2]
car(["foo", 1, 2]); // => "foo"

Fungsi variadik sebagai argumen

function apply<...T,U>(f: (...args:...T) => U, args: ...T): U {
    return f(...args);
}

function f(x: number, y: string) {
}
function g(x: number, y: string, z: string) {
}

apply(f, [1, 'foo']); // ok
apply(f, [1, 'foo', 'bar']); // too many arguments
apply(g, [1, 'foo', 'bar']); // ok
function curry<...T,...U,V>(f: (...args:[...T,...U]) => V, ...ts:...T): (...us: ...U) => V {
    return us => f(...ts, ...us);
}
let h: (...us: [string, string]) = curry(f, 1);
let i: (s: string, t: string) = curry(f, 2);
h('hello', 'world');
function compose<...T,U,V>(f: (u:U) => U, g: (ts:...T) => V): (args: ...T) => V {
    return ...args => f(g(...args));
}
function first(x: number, y: number): string {
}
function second(s: string) {
}
let j: (x: number, y: number) => void = compose(second, first);
j(1, 2);

TODO: Bisakah f mengembalikan ...U alih-alih U ?

dekorator

function logged<...T,U>(target, name, descriptor: { value: (...T) => U }) {
    let method = descriptor.value;
    descriptor.value = function (...args: ...T): U {
        console.log(args);
        method.apply(this, args);
    }
}

Pertanyaan-pertanyaan terbuka

  1. Apakah kisah penetapan tuple-to-parameter-list bertahan? Ini sangat goyah di sekitar parameter opsional dan istirahat.
  2. Akankah tipe yang disimpulkan menjadi penyatuan tupel seperti kasus parameter opsional? Karena bind , call dan apply adalah metode yang didefinisikan pada Fungsi, argumen tipenya harus diikat pada waktu pembuatan fungsi daripada situs panggilan bind (Misalnya). Tetapi ini berarti bahwa fungsi dengan kelebihan tidak dapat mengambil atau mengembalikan tipe khusus untuk argumennya -- mereka harus merupakan gabungan dari tipe kelebihan. Selain itu, Fungsi tidak memiliki konstruktor yang menentukan argumen tipe secara langsung, jadi tidak ada cara untuk memberikan tipe yang benar ke bind et al. TODO: Tambahkan contoh di sini. Perhatikan bahwa masalah ini tidak selalu unik untuk fungsi variadik.
  3. Haruskah parameter rest menjadi case khusus untuk mempertahankan sintaks panggilan Nice mereka, bahkan ketika mereka dihasilkan dari tipe Tuple? (Dalam proposal ini, fungsi yang diketik oleh jenis Tuple harus meneruskan array ke parameter lainnya, mereka tidak boleh memiliki parameter tambahan.)
Fix Available In Discussion Suggestion

Komentar yang paling membantu

Masalah ini sekarang diperbaiki oleh #39094, dijadwalkan untuk TS 4.0.

Semua 265 komentar

+1, ini sangat berguna untuk pemrograman fungsional di TypeScript! Bagaimana ini bekerja dengan argumen opsional atau sisanya? Lebih konkret, dapatkah fungsi compose digunakan pada fungsi dengan argumen istirahat atau argumen opsional?

Poin bagus. Saya pikir Anda dapat menetapkan tipe Tuple terkecil yang diizinkan ke fungsi param opsional karena Tuple hanyalah objek, yang memungkinkan anggota tambahan. Tapi itu tidak ideal. Saya akan melihat apakah saya dapat menemukan contoh compose dan kemudian saya akan memperbarui proposal.

Sebenarnya tipe serikat pekerja mungkin akan bekerja lebih baik. Sesuatu seperti

function f(a: string, b? number, ...c: boolean[]): number;
function id<T>(t: T): T;
let g = compose(f, id): (...ts: ([string] | [string, number] | [string, number, boolean[]]) => number

g("foo"); // ok
g("foo", 12); // ok
g("foo", 12, [true, false, true]); // ok

Ini masih merusak parameter istirahat.

@ahejlsberg , Anda punya beberapa ide bagaimana jenis Tuple akan bekerja, saya pikir.

Jadi :+1: tentang ini. Sebagai informasi ini terkait dengan (dan akan memenuhi) #3870. Kami telah mencoba menerapkan API jenis penulisan di TypeScript tetapi harus mengatasi beberapa batasan yang disebutkan dalam proposal ini. Ini pasti akan memecahkan beberapa masalah itu!

Tampaknya kadang-kadang Anda mungkin ingin "menggabungkan" jenis Tuple seperti itu alih-alih mempertahankannya, terutama dengan sesuatu seperti compose. Sebagai contoh:

function compose<T, ...U>(base: T, ...mixins: ...U): T&U {
    /* mixin magic */
}

Juga, dalam banyak contoh Anda, Anda telah menggunakan primitif. Bagaimana Anda melihat sesuatu yang lebih kompleks bekerja, terutama jika ada konflik?

Sayangnya proposal ini apa adanya tidak membahas #3870 atau komposisi tipe, karena satu-satunya operator komposisi untuk jenis tuple adalah [T,...U] . Anda juga dapat menulis ini sebagai T + ...U (yang lebih menunjukkan apa yang terjadi pada tipe), tetapi #3870 dan perpustakaan komposisi tipe Anda memerlukan T & ...U . Saya pikir itu mungkin, tetapi saya perlu memahami ide @JsonFreeman dan @jbondc dari #3870 terlebih dahulu. Saya akan memperluas proposal jika saya dapat mengetahui cara kerjanya.

Catatan: Saya memutuskan untuk menggunakan sintaks [...T, ...U] karena terlihat seperti sintaks penyebaran nilai yang setara, tetapi T + ...U lebih menunjukkan apa yang terjadi dengan jenisnya. Jika kita mendapatkan keduanya, maka + dan & mungkin merupakan operator yang akan digunakan.

Besar :+1: ini!

+1 luar biasa! Itu akan memungkinkan untuk mengekspresikan hal-hal seperti itu jauh lebih ekspresif dan ringan.

Maksud saya di #3870 tampaknya menjadi masalah di sini. Secara khusus, saya khawatir tentang menyimpulkan argumen tipe untuk parameter tipe variadik.

Inferensi argumen tipe adalah proses yang agak rumit, dan telah berubah secara halus dari waktu ke waktu. Ketika argumen dicocokkan dengan parameter untuk menyimpulkan argumen tipe tipe, tidak ada jaminan tentang urutan kandidat yang disimpulkan, atau berapa banyak kandidat yang disimpulkan (untuk parameter tipe tertentu). Ini umumnya tidak menjadi masalah karena hasil yang muncul ke pengguna tidak (dalam banyak kasus) mengekspos detail ini. Tetapi jika Anda membuat tipe Tuple dari hasil inferensi, itu tentu saja mengekspos urutan dan jumlah inferensi. Detail ini tidak dimaksudkan untuk diamati.

Seberapa serius ini? Saya pikir itu tergantung pada bagaimana tepatnya inferensi bekerja. Apa hasil dari berikut ini:

function f<...T>(x: ...T, y: ...T): ...T { }
f(['hello', 0, true], [[], 'hello', { }]); // what is the type returned by f?

@jbondc , - sepertinya ide yang bagus. Saya akan mengingatnya tetapi tidak menjelajahinya di sini, karena saya pikir kita harus memperkenalkan operator tipe baru satu per satu. Baik & dan + membuat tipe baru, tetapi & membuat tipe persimpangan sedangkan + membuat tipe Tuple baru (itulah sebabnya saya lebih suka sintaks [T,...U] bukannya T + ...U , karena [T,U] sudah melakukan ini untuk tipe).

@JsonFreeman Saya pikir tidak apa-apa untuk melakukan salah satu dari dua hal dengan parameter jenis berulang:

  1. Gabungkan jenisnya: f(['hello', 1], [1, false]): [string | number, number | boolean]
  2. Larang inferensi parameter jenis tuple berulang, terutama jika inferensi argumen tipe terbukti rumit. Sesuatu seperti ini:
f(['hello', 1], [1, false]) // error, type arguments required
f<[string, number]>(['hello', 1], [1, false]) // error, 'number' is not assignable to 'string'
f<[string | number, number | boolean]>(['hello', 1], [1, false]); // ok

Saya pikir perpustakaan nyata (seperti ekstensi reaktif yang ditautkan ke @Igorbek ) biasanya hanya akan memiliki satu parameter jenis Tuple sehingga meskipun baik (1) maupun (2) tidak dapat digunakan secara khusus, itu tidak akan banyak memengaruhi kode dunia nyata.

Dalam contoh di atas, curry adalah yang paling sulit untuk disimpulkan -- Anda harus melewati f: (...args:[...T,...U]) => V , menyimpulkan ...ts:...T , lalu kembali dan setel ...U ke apa tersisa setelah mengkonsumsi ...T dari parameter f .

Saya sudah mulai membuat prototipe ini (sandersn/TypeScript@1d5725d), tetapi belum sejauh itu. Adakah ide jika itu akan berhasil?

Saya akan berbuat salah di sisi melarang apa pun di mana semantik tidak jelas (seperti kesimpulan berulang ke parameter tipe menyebar yang sama). Itu menghilangkan kekhawatiran saya di atas juga.

Saya tidak bisa memikirkan mekanisme yang baik untuk mengetik kari. Seperti yang Anda tunjukkan, Anda harus melewati daftar parameter dari fungsi pertama untuk menggunakan argumen ...T dan kemudian melihat apa yang tersisa. Harus ada beberapa kebijakan untuk menunda inferensi ke parameter tipe menyebar jika tidak final dalam daftarnya. Itu bisa menjadi berantakan.

Yang mengatakan, saya pikir ini patut dicoba. Ada permintaan tinggi untuk fitur tersebut.

Saya pikir Anda harus melewati beberapa jenis Tuple yang muncul dalam konteks yang sama (misalnya tingkat atas seperti (...T,string,...U) => V atau digabungkan seperti [...T,...U,...T] ). Kemudian Anda dapat membuat beberapa operan pada jenis yang dilewati, menghilangkan jenis yang sudah disimpulkan dan jenis yang dilewati ulang yang masih ambigu. Jika suatu saat tidak ada jenis tunggal yang tersedia untuk inferensi, hentikan dan kembalikan kesalahan.

Jadi, ya. Rumit.

Anda mungkin dapat menarik inspirasi dari masalah serupa. Ini sebenarnya agak mirip dengan masalah menyimpulkan serikat atau persimpangan. Saat menyimpulkan ke tipe gabungan yang menyertakan parameter tipe yang merupakan anggota konteks inferensi, seperti pada function f<T>(x: T | string[]) , Anda tidak tahu apakah akan menyimpulkan ke T. Manifestasi yang dimaksud dari tipe gabungan mungkin adalah string[] . Jadi TypeScript pertama-tama menyimpulkan semua konstituen lain, dan kemudian jika tidak ada kesimpulan yang dibuat, menyimpulkan T.

Dalam kasus perpotongan, ini bahkan lebih sulit karena Anda mungkin harus membagi jenis argumen di seluruh konstituen perpotongan yang berbeda. TypeScript tidak membuat kesimpulan untuk jenis persimpangan sama sekali.

Bagaimana jika Anda hanya mengizinkan penyebaran Tuple jika itu adalah tipe terakhir dalam urutannya? Jadi [string, ...T] akan diizinkan, tetapi [...T, string] tidak akan diizinkan?

Jika saya mengerti dengan benar, ini sebenarnya akan menyelesaikan cerita mixin di TypeScript. Apakah saya benar dalam pemahaman ini?

Mungkin. Bisakah Anda memberikan contoh? Saya tidak fasih dengan pola mixin.

Sintaks dari variabel jenis variadik adalah ...T di mana T adalah pengidentifikasi yang dengan konvensi huruf besar tunggal, atau T diikuti oleh pengenal PascalCase.

Bisakah kita menyerahkan kasus pengidentifikasi parameter tipe ke pengembang?

@aleksey-bykov +1. Saya tidak melihat alasan mengapa itu tidak seharusnya terjadi.

Pengembang dengan latar belakang Haskell akan menghargai itu.

Maaf, kalimat itu dapat diuraikan secara ambigu. Maksud saya 'atau' untuk menguraikan dengan ketat: "dengan konvensi (satu huruf besar || T diikuti oleh pengenal PascalCase)". Saya tidak mengusulkan membatasi kasus pengidentifikasi, hanya menunjukkan konvensi.

Namun, untuk apa nilainya, _I_ memiliki latar belakang Haskell dan saya tidak suka melanggar konvensi bahasa yang saya gunakan untuk menulis.

Maaf untuk menggagalkan. Pertanyaan penasaran terakhir saya (jika Anda tidak keberatan saya bertanya) apa "konvensi" TypeScript yang mungkin rusak dan siapa yang peduli?

@sandersn

Ini harus mengetik centang, dengan asumsi T & ...U berarti T & U & V & ... (yang merupakan perilaku intuitif).

function assign<T, U, ...V>(obj: T, src: U, ...srcs: ...V): T & U & ...V {
  if (arguments.length < 2) return <T & U & ...V> obj

  for (const key of Object.keys(src)) {
    (<any> obj)[key] = (<any> src)[key]
  }

  if (arguments.length === 2) return <U> obj
  return mixin<T, ...V>(obj, ...srcs)
}

Atau dalam file definisi:

interface Object {
    assign<T, U, ...V>(host: T, arg: U, ...args: ...V): T & U & ...V
}

@aleksey-bykov konvensi yang saya bicarakan adalah kasus pengidentifikasi parameter tipe. Siapa yang peduli? Orang yang harus membaca kode TypeScript baru yang belum pernah mereka lihat sebelumnya -- konvensi membantu pembaca baru memahami kode baru lebih cepat.

@sandersn Apa yang mendapat kesan dari @aleksey-bykov adalah bahwa yang berikut ini _secara sintaksis_ tidak valid:

function assign<a, b, ...cs>(x: a, y: b, ...zs: ...cs): a & b & ...cs;

@isiahmeadows & dan | operasi atas jenis tidak tercakup dalam proposal ini, meskipun saya harus menambahkannya ke pertanyaan terbuka/pekerjaan di masa depan jika belum. Saat ini satu-satunya operator yang diusulkan adalah penggabungan: [THead, ...TTail] .

Satu perbedaan adalah bahwa penggabungan masih menghasilkan tipe tuple sementara & dan | masing-masing menghasilkan tipe persimpangan dan serikat.

@sandersn Contoh assign di TypeScript akan sepele untuk diubah dengan itu.

Meskipun:

  1. Persimpangan akan mirip dengan penggabungan, meskipun lebih seperti menggabungkan kamus daripada menggabungkan daftar. Jenis variadik mungkin diimplementasikan di atas mesin yang ada di sana.
  2. Persatuan akan seperti persimpangan, kecuali itu hanya menjaga bagian-bagian umum. Sekali lagi, tipe variadik mungkin diimplementasikan di atas mesin yang ada.

@isiahmeadows Persimpangan secara umum bukanlah rangkaian kamus. Itu hanya berlaku untuk persimpangan tipe objek, tetapi tidak misalnya persimpangan serikat pekerja. Serikat pekerja juga tidak sama dengan hanya mengambil properti yang dimiliki objek yang sama. Keduanya lebih baik dicirikan oleh seperangkat nilai yang menghuninya.

@sandersn Saya agak bingung tentang inferensi argumen tipe dengan tipe variadic. Apa yang harus disimpulkan di sini?

function foo<...T>(...rest: ...T): ...T { }
foo('str', 0, [0]);

Apakah hasilnya [string, number, number[]] ? Itu berarti Anda harus mengandalkan inferensi argumen tipe yang menambahkan kandidat dalam urutan kiri-ke-kanan, yang bukan asumsi sepele. Ini juga akan menjadi pertama kalinya sistem tipe memunculkan daftar kandidat inferensi kepada pengguna.

Saya tahu itu adalah proposal eksperimental/awal, tetapi kita dapat mendiskusikan sintaks ...T untuk parameter lainnya. Dari sudut pandang saya, itu tidak benar-benar berfungsi.
Jadi sintaks yang diusulkan adalah:

declare function f<...T>(...a: ...T);

mari kita bandingkan dengan sintaks parameter istirahat yang ada:

declare function f(...a: number[]);

jadi jenis parameter a yang menangkap argumen sisanya adalah number[] , jadi kita dapat dengan jelas memahami bahwa itu adalah sebuah array. Dengan analogi, saya dapat menyimpulkan bahwa ...T dari proposal juga mewakili array. Tapi itu tidak terlalu jelas.
Selanjutnya, katakanlah kita dapat mendefinisikan parameter istirahat yang lebih ketat:

declare function f(...a: [number, string]);
// same as
declare function f(c: number, d: string); // or very close to

Jadi sekarang, kita masih melihat bahwa tipe a adalah Tuple (yang merupakan array).

Proposal saya adalah menggunakan cara yang lebih konsisten untuk memperlakukan gagasan ...T untuk direpresentasikan sebagai "beberapa daftar tipe abstrak yang dipesan". Dan menggunakannya dengan cara yang sama seperti kita menggunakan operator spread:

var a: [number, string] = [1, "1"];
var b = [true, ...a]; // this must be [boolean, number, string], but it doesn't work :)

Jadi ...a dalam hal variabel, hanya 1, "1" .

Sintaks saya untuk mendefinisikan parameter istirahat dengan gagasan ...T :

declare function f<...T>(...a: [...T]);
declare function g<H, ...T>(head: H, ...tail: [...T]): [H, ...T];

Bagi saya itu jauh lebih masuk akal.

@Igorbek Saya telah menjalankan asumsi declare function f<...T>(...a: ...T); sudah bekerja seperti itu. Tapi saya tidak melihat declare function f(...a: [number, string]); mendapatkan banyak manfaat.

Agar lebih jelas.

Sintaks yang awalnya diusulkan untuk parameter istirahat:

function func<...T>(...a: ...T)

Jika saya bisa melakukan ini

function g<...T>(...a: ...T): [number, ...T] { ... }

maka saya akan dapat melakukan ini:

function f<...T>(...a: ...T): [...T] { return a; }

Jadi jenis a adalah [...T] (kami mengembalikannya), tetapi kami mendefinisikannya sebagai ...T di tanda tangan.
Kita dapat mengatakan bahwa ...T dan [...T] adalah sama, tetapi tidak berfungsi dalam hal variabel.
Untuk variabel:

var a = [1, 2];
[a] === [[1,2]];
[...a] === [1, 2];
f(...a) === f(1, 2)
...a === 1, 2 // virtually

Jika kita menerapkan hal yang sama pada parameter istirahat standar

function f(...a: number[]): number[] { return a; }

jenis a adalah number[] (berdasarkan jenis pengembalian), sama seperti yang didefinisikan dalam tanda tangan.

@isiahmeadows ya, function f(...a: [number, string]) tidak berfungsi. Saya baru saja mengembangkan pemikiran tentang bagaimana kita dapat memperlakukan parameter istirahat.

Jadi, melangkah lebih jauh. Untuk secara eksplisit mendefinisikan parameter tipe, sintaks berikut diusulkan:

function f<...T, ...U>()
f<[number, string], [boolean, number]>();

Ternyata:

f<...[number, string], ...[boolean, number]>();

Jadi ini mungkin berhasil juga:

function g<T1, T2, T3>()

g<A, B, C>();
// same as
g<...[A, B, C]>();
g<...[A], ...[B, C]>(); 
g<...[A], B, C, ...[]>();

@JsonFreeman begitulah cara kerja prototipe saya, ya. Tapi saya tidak cukup akrab dengan algoritma inferensi tipe untuk memahami mengapa itu berhasil. Dengan kata lain, pertanyaannya bukanlah apakah inferensi kiri-ke-kanan adalah asumsi _sepele_ tetapi benar. Untuk kasus identitas jawabannya adalah ya tetapi saya tidak tahu apakah Anda dapat membuat kasus di mana jawabannya tidak.

Anda juga dapat mengerjakan contoh kumpulan kandidat inferensi tipe yang terbuka? Seperti yang saya katakan, saya tidak memahami cara kerja algoritma inferensi dengan baik, jadi sebuah contoh akan membantu saya memahami maksud Anda.

Dan bahkan lebih baik:

function<...T>(...a: T): T;
// same as
function<...T>(...a: [...T]): T;

Saya menyarankan untuk memberi awalan [] ke pengidentifikasi tipe untuk menandakan sisa params tipe.

function fn<R, []T>(...a:[]T): R;

Ini 1 karakter lebih pendek dari ...T dan (menurut saya) membuat lebih sedikit noise visual.

@aleksey-bykov Saya sebenarnya memiliki pendapat yang berlawanan tentang itu. Itu tidak cocok dengan sintaks parameter istirahat yang ada, jadi saya yakin itu juga kurang jelas dari pandangan sekilas.

[...T] / T sebagai tipe parameter array lainnya tampaknya jauh lebih baik bagi saya. Sekali lagi, bandingkan dengan array dan operator spradnya:

| array | jenis (dari proposal) | jenis (pembaruan saya) |
| --- | --- | --- |
| var x = [1,2] | tidak | T = [T1, T2] |
| [0, ...x] === [0,1,2] | [T0, ...T] === [T0, T1, T2] | [T0, ...T] === [T0, T1, T2] |
| f(x) === f([1, 2]) | tidak | f<T>() === f<[T1, T2]>() |
| f(...x) === f(1, 2) | f<...T>() === f<[T, T2]> ? | f<...T>() === f<T1, T2> |
| f(0, ...x) === f(1, 2) | f<T0, ...T>() === f<T0, [T, T2]> ? | f<T0, ...T>() === f<T0, T1, T2> |

Dari proposal

function g<...T>(...x: ...T) {
 // being called as g(1, "a");
  var a: ...T; // [number, string] ?
  var b: [number, ...T]; // [number, number, string]
  var c: [...T]; // [number, string] - same as a ? so [...T] is same as ...T - weird
}

Dari pembaruan saya

function g<...T>(...x: T) {
 // being called as g(1, "a");
  var a: T; // [number, string]
  var b: [number, ...T]; // [number, number, string]
  var c: [...T]; // [number, string]
}

Pembaruan terlihat lebih bagus sekarang IMO. Daftar untuk mewakili tipe terdengar sangat bagus, tetapi bahkan Lisps yang diketik tidak sejauh itu (tipe homoikon, siapa saja? :senyum :).

Saya mendapatkan daya pikat kemurnian, tetapi saya juga melihat aspek pragmatis juga. Daftar juga akan relatif mudah diterapkan sendiri, tetapi tidak cocok dengan bahasa lainnya. Ini hampir seperti banyak upaya untuk mengimplementasikan monad di Java (bahasa) atau lambdas di C - mereka selalu menjadi sangat jelek dan meretas.

@sandersn Saya dapat mencoba menjelaskan apa yang saya maksud dengan memaparkan daftar kandidat. Inferensi argumen tipe menghasilkan daftar kandidat untuk setiap parameter tipe. Kemudian ia memeriksa apakah ada kandidat yang merupakan supertipe dari kandidat lainnya, dan jika demikian, kandidat tersebut adalah pemenangnya. Jadi dalam contoh berikut:

function foo<T>(a: T, b: T): T {}
foo(["hi", 0], ["", ""]);

Argumen akan diketik, dan kemudian disimpulkan ke setiap parameter. Dua kandidat akan dihasilkan, yaitu (string | number)[] dan string[] . Tetapi yang pertama akan menang karena itu adalah supertipe dari yang kedua. Dan sebagai hasilnya, pengguna tidak pernah mengamati bahwa string[] pernah ada dalam gambar. Ada satu kesimpulan untuk T , dan semua kandidat lainnya tidak terlihat. Artinya ada dua hal yang tidak terlihat oleh pengguna, yaitu urutan calon dan banyaknya calon.

Berikut adalah masalah dengan multiplisitas jika Anda mengandalkan daftar kandidat sebagai daftar elemen Anda dalam Tuple yang dilambangkan dengan ...T :

function foo<...T>(...rest: ...T): ...T
foo(0, 1);

Saya pikir Anda ingin menyimpulkan [number, number] untuk T mengingat maksud proposal Anda seperti yang saya pahami. Tetapi karena berisi check in line https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L6256 , kandidat number hanya akan ditambahkan satu kali, dan T akan disimpulkan sebagai [number] . Ini adalah masalah multiplisitas yang saya bicarakan.

Adapun urutannya, dari kiri ke kanan. Tetapi ada beberapa lintasan, dan argumen akan diproses ulang jika mengandung ekspresi fungsi yang akan diketik secara kontekstual. Jika ada n argumen yang berisi ekspresi fungsi yang diketik secara kontekstual, maka ada n + 1 melewati argumen. Contohnya adalah Array.prototype.reduce, di mana parameter initialValue diketik dan disimpulkan secara efektif sebelum panggilan balik, meskipun faktanya ada di sebelah kanan. Jadi sesuatu seperti berikut ini mungkin menjadi masalah untuk proposal:

function foo<...T>(...rest: ...T): ...T
foo(x => x, 0);

Secara intuitif, T seharusnya [(x: any) => any, number] , tetapi jika Anda mengandalkan urutan kandidat yang ditambahkan, itu akan menjadi [number, (x: any) => any] . Ini karena inferensi argumen tipe biasanya dibiarkan dari kiri ke kanan, tetapi fungsi yang tunduk pada pengetikan kontekstual ditangguhkan hingga akhir.

Baik masalah multiplisitas maupun urutan yang telah saya jelaskan adalah contoh munculnya daftar kandidat. @ahejlsberg pasti akan menjadi orang yang baik untuk bertanya tentang ini juga, dan memang dia dapat membantu menjelaskan, mengkonfirmasi atau menyangkal apa pun yang saya katakan.

@JsonFreeman mengapa menurut Anda itu akan menjadi masalah?
Ini dapat diimplementasikan dengan secara virtual memperkenalkan tipe generik tambahan untuk setiap argumen faktual sisanya dan menyimpulkan fungsi dengan panjang parameter tetap.
Sebagai contoh,

function foo<...T>(...rest: T) { ... }
foo(x => x, 0);
// to infer, the following function is used
function foo2<T0, T1>(rest0: T0, rest1: T1) { ... }
foo2(x => x, 0);
// inferred as
foo2<(x: any) => any, number>
// T0 = (x: any) => any
// T1 = number
// T = [T0, T1] = [(x: any) => any, number]

BTW, bisakah kita menyimpulkan x => x bertipe { <T>(x: T): T; } ?

@Igorbek Saya pikir saran Anda tentang parameter tipe manufaktur (setidaknya sebagai intuisi, terlepas dari bagaimana penerapannya) adalah cara yang benar untuk melakukannya. Anda dapat menyimpulkan urutan tipe untuk T , di mana setiap elemen dalam urutan memiliki indeks dan daftar kandidat (ini adalah cara alternatif untuk menerapkan apa yang Anda sebutkan).

Namun, maksud saya adalah, saya tidak berpikir ini yang akan terjadi secara alami jika Anda hanya menggunakan kembali daftar kandidat inferensi sebagai Tuple yang disimpulkan. Ini akan membutuhkan mekanika eksplisit untuk membuat hal yang benar terjadi.

Untuk poin Anda tentang { <T>(x: T): T; } , itu tidak digeneralisasi dengan baik untuk mengetik hal-hal seperti x => foo(x) mana foo adalah beberapa fungsi. Anda perlu mengetahui jenis x untuk melakukan resolusi kelebihan untuk foo .

Sebuah langkah kecil keluar dari pertempuran dengan aturan inferensi tipe-pemeriksa.
Saya punya komentar/saran tentang sintaks. Saya pikir ada dua opsi yang konsisten tetapi saling eksklusif:

1. Argumen tipe istirahat formal

Jika kita memilih formulir ini:

type F<...Args> = (...args:...Args) => ...Args

maka kita harus menggunakannya seperti

var a:  F // a: () => []
var b:  F<number> // b: (arg: number) => [number]
var c:  F<number, string> // c: (arg1: number, arg2: string) => [number, string]
...

Dengan demikian akan benar istirahat tipe formal. Mereka harus digunakan hanya pada posisi terakhir dari bagian parameter tipe formal.

2. Argumen istirahat yang diketik Tuple

(...args:[string, number]) => boolean    IS EQUIVALENT TO   (s: string, n: number) => boolean

Dalam hal ini kami selalu memiliki jumlah slot yang tetap di bagian parameter tipe formal.

function f<T>(...args: T): T {
    return args;
}

kami menyimpulkan bahwa T harus menjadi tipe Tuple jika salah satu kondisi terpenuhi:

  1. T digunakan untuk parameter istirahat seperti (...args: T) => T
  2. T digunakan dalam komposisi spread seperti [...T] atau [number, ...T, string]

Jadi, kita tidak perlu menggunakan elipsis di bagian parameter tipe formal (kita dapat menyimpulkannya bahkan _sintaksis_ tanpa pemeriksa tipe apa pun)

dalam hal ini, kita juga dapat menulis

function f<T>(...args: [...T]): [...T] {
    return args;
}

tapi itu berlebihan.

Secara pribadi, saya ingin melihat yang nanti diimplementasikan dalam TypeScript. @JsonFreeman , @sandersn?

@Artazor Saya pikir itu bermuara pada ekspresivitas, dan saya tidak berpikir kedua pendekatan itu harus setara. Yang kedua mencakup kemampuan untuk menyebarkan parameter tipe istirahat di dalam tipe Tuple, sedangkan yang pertama tampaknya tidak.

Saya pikir untuk referensi tipe generik, ini hanya masalah memutuskan di mana dan secara sintaksis bagaimana menggunakan parameter tipe istirahat. Ini perlu diputuskan untuk semua konstruktor tipe yang mengambil urutan tipe (tupel, tanda tangan, referensi tipe generik).

Untuk tanda tangan generik, ini lebih rumit karena inferensi argumen tipe. Bagaimana jika Anda memiliki hal berikut:

function callback(s: string, n: number): void { }
declare function foo<...T>(cb: (...cbArgs: T) => void, ...args: T): [...T];

foo(callback, "hello", 0, 1);

Apa yang dikembalikan foo? Maksud saya hanyalah bahwa orang mengharapkan aturan generik sama untuk tipe generik dan tanda tangan generik, tetapi jika Anda membuat tipe generik lebih ekspresif, inferensi argumen tipe memerlukan cara untuk menanganinya. Ini mungkin hanya masalah mengidentifikasi secara formal kasus yang sulit untuk inferensi argumen tipe, dan mengharuskan pengguna untuk memberikan argumen tipe eksplisit dalam kasus ini.

Menurut pendapat saya, saya pikir opsi 1 Anda lebih baik. Saya pribadi tidak melihat penggunaan menggunakan tipe Tuple sebagai parameter istirahat. Saya pikir parameter rest hanya boleh berupa tipe array, atau parameter tipe rest, karena seharusnya memiliki panjang variabel. Saya juga menyukai konsep parameter tipe istirahat menjadi urutan tipe abstrak, tidak terkait dengan sesuatu yang sudah ada dalam sistem tipe.

Filosofi saya tentang tupel adalah bahwa mereka mewakili subset dari nilai array di mana panjangnya diketahui. Nilai-nilai array tersebut adalah entitas runtime nyata. Saya tidak suka ide menggunakannya sebagai semacam perangkat sistem tipe untuk mewakili urutan tipe abstrak (misalnya urutan parameter dalam tanda tangan). Tetapi apakah Anda diizinkan untuk menyebarkan parameter tipe istirahat di Tuple adalah cerita yang berbeda.

Saya suka proposal Tuple karena lebih kuat dan menyelesaikan lebih banyak kasus penggunaan, juga sangat intuitif bahwa saya dapat menyebarkan Tuple sebagai parameter istirahat karena tupel hanyalah array dan ketika memanggil fungsi dengan parameter istirahat saya dapat menyebarkan array. Sistem tipe kemudian akan lebih cocok dengan pemahaman saya tentang kode.

@JsonFreeman dalam kasus Anda foo akan mengembalikan [string, number, number] karena itu akan disimpulkan dari ...args , tipe cb yang disimpulkan adalah (string, number, number) => void dan panggilan balik yang diteruskan hanya akan mengabaikan argumen terakhir yang sangat umum di kedua TS dan JS.

Saya tidak suka ide menggunakannya semacam perangkat sistem tipe untuk mewakili urutan tipe abstrak

Itulah tepatnya mereka, JS tidak tahu tentang tupel, hanya TS. Untuk TS sebuah tuple adalah urutan tipe.

Saya suka pendekatan berbasis Tuple juga. Terutama jika kita dapat memiliki tanda tangan fungsi yang kompatibel seperti itu:

// all are equivalent
(a: A, b: B, c: C) => R;
(a: A, b: B, ...rest: [C]) => R;
(a: A, ...rest: [B, C]) => R;
(...args: [A, B, C]) => R;

// this is more complicated 
(a: A, ...rest: T[]) => R;
(...args: [A, ...T]) => R; // no in current syntax

Yang terakhir tidak dapat kami ekspresikan dengan sintaks saat ini tetapi bisa jika kami mengadopsi #6229.
Jadi bagi saya sepertinya cara yang tepat adalah menggunakan tupel dan menyatukan tupel untuk mengekspresikan lebih banyak. Tanpa tupel yang lebih ekspresif, akan sulit untuk memiliki sesuatu seperti [...T, ...T] karena T sebagai tupel memiliki panjang terbuka.

@JsonFreeman untuk contoh Anda, @Pajn menunjukkan persis seperti pemahaman saya tentang itu - tidak ada masalah yang terlihat dalam menyimpulkan jenis ini.

@JsonFreeman sebaiknya saya menggunakan sintaks itu

declare function foo<T>(cb: (...cbArgs: T) => void, ...args: T): T;
declare function foo<T>(cb: (...cbArgs: T) => void, ...args: T): [...T]; // same

Hm, mungkin itu bisa menimbulkan beberapa ambiguitas:

declare function foo<T>(...args: T): T;
foo(1); // T is [number] or number[]?

// however, here it'd be more explicit
declare function foo<T>(...args: T[]): T[];
foo(1); // T is number[]

// and here
declare function foo<T>(...args: [...T]): T;
foo(1); // T is [number]

Saya bisa mendapatkan ide untuk menyebarkan parameter tipe istirahat di Tuple. Tapi saya tidak yakin saya ingin parameter tipe istirahat ditafsirkan secara implisit sebagai Tuple. Contoh @Pajn masih akan berfungsi jika parameter tipe istirahat diizinkan untuk disebarkan di semua posisi urutan tipe (tupel, daftar parameter, argumen tipe).

@Igorbek Anda benar tentang ambiguitas dalam contoh pertama Anda. Contoh ketiga Anda juga bermasalah. Diberikan urutan seperti number, string , ada 2 kemungkinan instantiasi tanda tangan. Yaitu (arg1: number, arg2: string) => [number, string] serta (arg1: [number, string]) => [number, string] (mengadopsi interpretasi Tuple implisit demi contoh).

Hal aneh lainnya tentang interpretasi Tuple implisit, adalah ini: katakanlah Anda memiliki parameter tipe istirahat T yang dipakai ke number, string . Sekarang katakan Anda meneruskannya sebagai argumen tipe, Foo<T> . Apakah itu ditafsirkan sebagai Foo<[number, string]> sedangkan Foo<...T> adalah Foo<number, string> ? Ada argumen untuk ini, karena akan memperluas operator spread ke sistem tipe. Tapi saya masih lebih suka versi Tuple direpresentasikan sebagai Foo<[...T]>

Sebut saya gila, tapi saya merasakan beberapa kelemahan mendasar dengan ide menggunakan
tupel. Apa yang terjadi jika Anda mencoba menyebarkan tipe Tuple ke terlalu banyak?
parameter? Seperti ini?

declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

Juga, apa yang terjadi jika parameter tipe dari tipe yang salah atau digunakan dalam
tempat yang tidak biasa dan berpotensi salah?

// 1. unusual place
declare foo<T>(x: T, ...ys: [...T]): void

// 2. bad type
declare foo<T>(...xs: [...T]): void
foo<number>(2)

Contoh pertama secara langsung relevan untuk Function#apply (dan bisa berupa
error), dan yang kedua adalah kesalahan yang tidak jelas yang akan gagal dikompilasi,
dan tidak sepele untuk dideteksi dengan Intellisense.

Pada Minggu, 28 Februari 2016, 03:04 Jason Freeman [email protected] menulis:

Hal aneh lainnya tentang interpretasi tupel implisit, adalah ini: say
anda memiliki parameter tipe istirahat T yang dipakai ke nomor, string.
Sekarang katakan Anda meneruskannya sebagai argumen tipe, Foo. Apakah itu menjadi?
ditafsirkan sebagai Foo<[angka, string]> sedangkan Foo<...T> adalah Foo tali>?


Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189817561
.

@JsonFreeman

Contoh ketiga Anda juga bermasalah. Diberikan urutan seperti number, string , ada 2 kemungkinan instantiasi tanda tangan. Yaitu (arg1: number, arg2: string) => [number, string] serta (arg1: [number, string]) => [number, string] (mengadopsi interpretasi Tuple implisit demi contoh).

Dari contoh ketiga saya yang jelas itu hanya bisa diartikan sebagai (...args: [number, string]) => [number, string] :

declare function foo<T>(...args: [...T]): T;
foo(1, "a"); // T is [number, string]
const result: [number, string] = foo<[number, string]>(1, "a");

// however, it is assignable to/from the following signatures:
const f1: (arg1: number, arg2: string) => [number, string] = foo<[number, string]>;
const f2: (arg1: number, ...rest: [string]) => [number, string] = foo<[number, string]>;

Hal aneh lainnya tentang interpretasi Tuple implisit, adalah ini: katakan Anda memiliki parameter tipe istirahat T yang dipakai ke number, string .

T tidak dapat diinstansiasi ke number, string karena ini adalah tupel yang sebenarnya. Itu harus [number, string] .

Sekarang katakan Anda meneruskannya sebagai argumen tipe, Foo<T> . Apakah itu ditafsirkan sebagai Foo<[number, string]> sedangkan Foo<...T> adalah Foo<number, string> ?

Benar. Namun, memiliki <...T> tampaknya berlebihan untuk kasus penggunaan khusus yang sedang kita diskusikan ini (tangkap tipe yang diposisikan untuk argumen istirahat). Namun demikian, katakanlah kita memilikinya.

Ada argumen untuk ini, karena akan memperluas operator spread ke sistem tipe. Tapi saya masih lebih suka versi Tuple direpresentasikan sebagai Foo<[...T]>

Ada dua kasus ketika kita mungkin menggunakan sintaks itu:

// in a signature declaration
declare function foo<[...T]>(...args: [...T]): [...T];
// and when type instantiated, so in the usage
type T = [number, string]
foo<T>();
foo<[...T]>();
// the latter can virtually be replaced as
type _T = [...T]; // which is a type operation that should produce [number, string]
foo<_T>();
// and more
type Extended = [boolean, ...T]; // [boolean, number, string]

Jadi untuk penggunaan tidak lebih dari jenis operator seperti | , & atau [] . Tetapi dalam deklarasi bahwa sintaks dapat diinterpretasikan sebagai T extends any[] atau tipe dasar apa pun untuk semua tupel, untuk menunjukkan bahwa itu harus menjadi tipe tuple.

@isiahmeadows

Apa yang terjadi jika Anda mencoba menyebarkan tipe Tuple ke terlalu banyak?
parameter? Seperti ini?

declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2); // ok, foo<[number]> is of type (...args: [number]) => void
// [1, 2] is being passed in place of args
// is [1, 2] which is [number, number] assignable to [number]? yes, with current rules
// no error

Juga, apa yang terjadi jika parameter tipe dari tipe yang salah atau digunakan dalam
tempat yang tidak biasa dan berpotensi salah?

// 1. unusual place
declare foo<T>(x: T, ...ys: [...T]): void
// 1. [...T] can be interpret as a type constraint "must be a tuple type"
// 2. if we call with type specified
foo<number>(1); // number doesn't meet constraint
foo<[number]>(1, 2); // argument of type 'number' is not assignable to parameter 'x' of type '[number]'
foo<[number]>([1], 2); // ok
// 3. if we call without type, it must be inferred
foo(1); // according to current rules, T would be inferred as '{}[]' - base type of all tuples
        // so, argument of type 'number' is not assignable to parameter 'x' of type '{}[]'
foo([1, 2], 2); // T is inferred as '[number, number]
                // rest arguments of type '[number]' are not assignable to rest parameters 'ys' of type '[number, string]'
foo([1], 2, 3); // T is '[number]',
                // x is of type '[number]',
                // ys is of type '[number]',
                // rest arguments are of type '[number, number]' which is assignable to '[number]',
                // no error

// 2. bad type
declare foo<T>(...xs: [...T]): void
foo<number>(2); // type 'number' doesn't meet constraint

Saya masih tidak melihat manfaat dari mewakili hal-hal ini sebagai tupel. Selanjutnya, saya pikir mereka harus dideklarasikan sebagai <...T> dan bukan <T> . Seperti yang saya katakan sebelumnya, saya tidak melihat tipe Tuple sebagai perangkat yang sesuai untuk digunakan untuk urutan tipe panjang sewenang-wenang dalam sistem tipe. Saya masih tidak yakin bahwa ini diperlukan untuk ekspresivitas yang diinginkan orang.

Saya setuju itu bisa lebih ekspresif, tetapi memiliki operator 'spread' di posisi parameter tipe akan membatasi kita untuk menangkap argumen istirahat hanya sekali, sama seperti kita tidak dapat memiliki parameter istirahat dua kali. Jadi mengingat <...T> dan <A, B, C> , T akan menangkap mereka sebagai [A, B, C] . Dan kami tidak akan dapat mengungkapkan <...T, ...U> karena akan menjadi ambigu - [A, B, C], [] atau [A, B], [C] atau ... dll.

Katakanlah saya ingin mengekspresikan fungsi dengan perilaku berikut:

declare function foo(a: A, b: B): R;
declare function boo(c: C, d: D, e: E): U;

let combined: (a: A, b: B, c: C, d: D, e: E) => [R, U] = combine(foo, boo);

// so the signature could be:

declare function combine<R, U, ???>(
  f1: (...args: [...T1]) => R,
  f2: (...args: [...T2]) => U):
    (...args: [...T1, ...T2]) => [R, U];

// if ??? is '...T1, ...T2'
combine<R, U, A, B, C, D, E> // what will be T1 and T2 ?
combine<R, U, ...[A, B, C], ...[D, E]> // ok ? so we will preserve spread to specific positions. so then
combine<...[R, U], A, ...[B, C, D], E> // will be restricted.
// however, ES6 allows to do it with function arguments
f(1, 2, 3);
f(...[1, 2], 3);
f(...[1], ...[2, 3]);

// if ??? is 'T1 extends TupleBase, T2 extends TupleBase'
// or just '[...T1], [...T2]' as a shortcut for such constraints
combine<R, U, [A, B, C], [D, E]> // pretty explicit, and doesn't occupy spread operator for type arguments

Oke, saya mengerti sekarang bagaimana Anda memikirkannya. Sepertinya apa yang Anda usulkan sebenarnya adalah fitur yang berbeda dari apa yang saya pikirkan. Alih-alih menambahkan konstruksi baru untuk menangkap urutan parameter tipe, Anda hanya ingin tipe Tuple dapat disebarkan karena sudah mewakili urutan tipe. Dengan cara itu dimungkinkan untuk melewatkan beberapa tupel dengan panjang yang berbeda dengan cara yang lebih transparan.

Dalam javascript, ini lebih seperti function foo([...rest]) { } daripada function foo(...rest) { } .

Itu lebih masuk akal bagi saya sekarang, terima kasih telah menjelaskan. Saya pikir itu adalah pendekatan yang masuk akal.

@JsonFreeman Tepat!

@JsonFreeman Pertanyaan: mengapa [1, 2] memuaskan [number] ? Itu terlihat sangat aneh bagi saya. Itu benar-benar bekerja akan sangat mengejutkan. Ini sama sekali bukan tipe yang aman.

Bukannya saya menentang tupel yang digunakan untuk tipe variadic (saya netral, terlalu jujur).

@isiahmeadows dengan cara apa [1, 2] tidak dapat disubstitusikan untuk [number] ? Ini pasti subtipe. Ini sama bagaimana { x: 1, y: 2 } adalah { x: number } valid

Oke. Saya akan mengakui sebagian, tetapi pertimbangkan Function.prototype.apply, yang menerima tupel argumen.

interface Function<T, U, V> {
    (this: T...args: [...U]): V;
    apply(object: T, args: U): V;
}

Jika pemanggil melontarkan TypeError pada terlalu banyak argumen, maka meneruskan terlalu banyak akan menghasilkan kesalahan runtime, bukan kesalahan kompilasi seperti yang seharusnya.

Bukankah sangat jarang ada fungsi JS yang melempar TypeError ketika memberikan terlalu banyak argumen? Apa saja contohnya?

@isiahmeadows sebagai contoh abstrak, saya mengerti bahwa kesalahan yang Anda khawatirkan adalah:

function f(x: number): void {
  // throw if too many arguments
}
f.apply(undefined, [1,2,3]); // runtime error, no compile-time error
f(1,2,3) // compile-time error and runtime error.

Apakah itu benar?

@sandersn , saya pikir TypeError pada terlalu banyak argumen adalah sesuatu yang melanggar semangat JS, karena kami biasanya melewati fungsi dengan argumen yang kurang formal daripada yang sebenarnya yang akan diteruskan ke fungsi ini. Kami hanya tidak menggunakannya. Misalnya Array.prototype.forEach

Bagaimana dengan fungsi kari? Itu mungkin jauh lebih umum, dengan Ramda
dan lodash/fp.

Pada Senin, 29 Februari 2016, 13:45 Anatoly Ressin [email protected] menulis:

@sandersn https://github.com/sandersn , saya pikir TypeError juga
banyak argumen adalah sesuatu yang melanggar semangat JS, seperti yang kita
biasanya melewati fungsi dengan argumen yang kurang formal daripada yang sebenarnya yang akan
diteruskan ke fungsi ini. Kami hanya tidak menggunakannya. Sebagai contoh
Array.prototype.forEach


Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190327066
.

@isiahmeadows Saya akan mengatakan bahwa kari berdasarkan arguments.length sangat tidak stabil dan rawan kesalahan runtime. Currying nyata adalah bukti ekstra-argumen:

var plus = x => y => x + y
console.log(plus(3)(4)) // 7
console.log(plus(3,10)(4,20)) // still 7

Ketika saya meneruskan fungsi saya dengan tanda tangan tetap sebagai panggilan balik ke suatu tempat, saya memikirkannya dengan cara berikut: 'fungsi saya mengharapkan _setidaknya_ argumen itu'

Bagaimana dengan hal-hal seperti foldl ?

const list = [1, 2, 3]
console.log(foldl((a, b) => a + b, 0, list))
console.log(foldl((a, b) => a + b, 0)(list))
console.log(foldl((a, b) => a + b)(0, list))
console.log(foldl((a, b) => a + b)(0)(list))

Itu sangat umum dalam pemrograman fungsional. Dan menghilangkan yang terakhir
argumen cukup umum.

Pada Senin, 29 Februari 2016, 13:52 Anatoly Ressin [email protected] menulis:

@isiahmeadows https://github.com/isiahmeadows Saya akan mengatakan itu kari
berdasarkan aruments.length sangat tidak stabil dan rawan kesalahan runtime.
Currying nyata adalah bukti ekstra-argumen:

var ditambah = x => y => x + y
console.log(plus(3)(4)) // 7
console.log(plus(3,10)(4,20)) // masih 7


Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Jika Anda ingin meneruskannya sebagai panggilan balik, katakanlah, map (mengerjakan daftar
daftar), Anda mungkin ingin mencicipinya.

Pada Senin, 29 Februari 2016, 13:59 Isiah Meadows [email protected] menulis:

Bagaimana dengan hal-hal seperti foldl ?

const list = [1, 2, 3]
console.log(foldl((a, b) => a + b, 0, list))
console.log(foldl((a, b) => a + b, 0)(list))
console.log(foldl((a, b) => a + b)(0, list))
console.log(foldl((a, b) => a + b)(0)(list))

Itu sangat umum dalam pemrograman fungsional. Dan menghilangkan yang terakhir
argumen cukup umum.

Pada Mon, Feb 29, 2016, 13:52 Anatoly Ressin [email protected]
menulis:

@isiahmeadows https://github.com/isiahmeadows Saya akan mengatakan itu kari
berdasarkan aruments.length sangat tidak stabil dan rawan kesalahan runtime.
Currying nyata adalah bukti ekstra-argumen:

var ditambah = x => y => x + y
console.log(plus(3)(4)) // 7
console.log(plus(3,10)(4,20)) // masih 7


Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

Saya pikir sebagian besar tentang itu:

type T = [number, string];
var a: T = [1, "a", 2]; // valid

// in this cases tuple types or parameter types cannot be inferred:
f(...a, true); // you could think number,string,boolean were passed, but weren't
const c = [...a, true]; // you could think that is of type [number, string, boolean] but it's not
// according to current rules, the best inferred types might be [number, string, number|string|boolean]

// same manner with variadic kinds, types are constructed properly:
type R = [...T, boolean]; // [number, string, boolean]

Itu sebabnya saya mengusulkan #6229

Pertanyaan apakah [1, 2] memenuhi [number] adalah pertanyaan yang valid untuk ditanyakan dan diperdebatkan. Tapi apa hubungannya dengan fitur tupel yang dapat disebarkan?

Apakah aplikasi variadik dari tupel harus mengabaikan argumen tambahan
atau tidak. Fungsi yang kelebihan beban ini harus menguraikan lebih banyak perhatian saya.

declare function foo(x: number, ...args: string[]): void
declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

// This will always fail
declare function foo(x: number, ...args: string[]): void
declare function foo<T>(x: T): void
foo<number>(1, 2)

Pada Senin, 29 Februari 2016, 18:47 Jason Freeman [email protected] menulis:

Pertanyaan apakah [1, 2] memenuhi [angka] adalah pertanyaan yang valid untuk ditanyakan
dan debat. Tapi apa hubungannya dengan fitur tupel yang dapat disebarkan?


Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

Dan inilah mengapa, untuk alasan praktis, saya lebih memilih rest parameter-like
tipe variadik.

Pada Senin, 29 Februari 2016, 19:00 Isiah Meadows [email protected] menulis:

Apakah aplikasi tupel variadik harus mengabaikan ekstra
argumen atau tidak. Fungsi kelebihan beban ini harus menguraikan lebih banyak dari saya
perhatian.

declare function foo(x: number, ...args: string[]): void


declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

// This will always fail
declare function foo(x: number, ...args: string[]): void
declare function foo<T>(x: T): void
foo<number>(1, 2)

Pada Senin, 29 Februari 2016, 18:47 Jason Freeman [email protected]
menulis:

Pertanyaan apakah [1, 2] memenuhi [angka] adalah pertanyaan yang valid untuk ditanyakan
dan debat. Tapi apa hubungannya dengan fitur tupel yang dapat disebarkan?


Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

@JsonFreeman itu karena spread operator untuk tipe dan array/tupel. Jika operator tipe spread diperbolehkan dalam bentuk "tipe yang diberikan A , B dan T = [A] , maka [...T, B] akan membangun [A, B] " (yang secara implisit diusulkan) maka itu tidak akan disejajarkan dengan operator array/tuple spread. Mengingat var a: [A] dan var b: B , tipe ekspresi [...a, b] tidak dapat dibuktikan sebagai tipe [A, B] . Menurut aturan tupel saat ini, itu dapat dibuktikan bertipe [A, A|B] .
Apakah itu masuk akal bagi Anda? Atau saya bisa membuat tabel perbandingan untuk menyoroti ketidakcocokan itu.

@Igorbek Saya mengerti apa yang Anda katakan. Ini pada akhirnya berasal dari fakta bahwa kompiler memiliki pengetahuan sempurna tentang jenis yang dihadapinya, tetapi bukan pengetahuan nilai yang sempurna. Khususnya, dalam contoh Anda, nilai a memiliki panjang yang tidak diketahui, sedangkan tipe [A] telah diketahui panjangnya. Ini adalah salah satu alasan saya awalnya tidak nyaman menggunakan tipe Tuple untuk tujuan ini. Tapi saya tidak yakin itu masalah serius.

@isiahmeadows Saya mengerti apa yang Anda tanyakan, tetapi mengapa masalahnya lebih jelas dengan parameter tipe istirahat? Jika Anda memiliki lebih banyak argumen daripada tipe argumen, pertanyaan yang sama dapat diajukan.

Jenis solusi aman akan lebih konsisten dengan sisanya
bahasa jika meniru sintaks argumen.

Maksud saya adalah jika Anda secara efektif menyebarkan parameter istirahat, Anda mendapatkan
persis jenis argumen dan tidak lebih. Fungsi kari memiliki pengembalian
tipe tergantung pada tipe argumen. Jadi jika Anda menerapkan satu argumen terlalu banyak
untuk menerapkan sebagian fungsi kari, Anda akan mendapatkan yang sama sekali berbeda
Tipe. Menangani jenis istirahat seperti tupel akan menyebabkan kesalahan runtime sebagai gantinya,
yang tidak pernah baik.

Pada Selasa, 1 Maret 2016, 06:07 Jason Freeman [email protected] menulis:

@Igorbek https://github.com/Igorbek Saya mengerti apa yang Anda katakan.
Ini pada akhirnya berasal dari fakta bahwa kompiler memiliki pengetahuan yang sempurna
dari jenis yang dihadapinya, tetapi bukan pengetahuan yang sempurna tentang nilai-nilai. Di dalam
tertentu, dalam contoh Anda, nilai a memiliki panjang yang tidak diketahui, sedangkan
tipe [A] telah diketahui panjangnya. Ini adalah salah satu alasan saya awalnya
tidak nyaman menggunakan tipe Tuple untuk tujuan ini. Tapi saya tidak yakin
itu masalah serius.

@isiahmeadows https://github.com/isiahmeadows Saya mengerti apa yang Anda minta
tentang, tetapi mengapa masalahnya lebih jelas dengan parameter tipe istirahat?


Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190667281
.

@isiahmeadows dapatkah Anda memberikan contoh kode untuk masalah kari?

Saya masih berpikir bahwa bahkan jika Anda menggunakan parameter tipe istirahat (yang saya setujui), Anda harus secara eksplisit memutuskan untuk tidak mengizinkan argumen berlebih, tetapi saya setuju dengan @isiahmeadows bahwa Anda mungkin harus melakukannya.

@sandersn @JsonFreeman

type FullCurry<T> = ((initial: T, xs: T[]) => T) | ((initial: T) => (xs: T[]) => T)
declare function foldl<T>(func: (acc: T, item: T) => T, initial: T, xs: T[]): T
declare function foldl<T>(func: (acc: T, item: T) => T): FullCurry<T>
declare function foldl<T>(func: (acc: T, item: T) => T, initial: T): (xs: T[]) => T

interface Function<T, R, ...A> {
    apply<U extends T>(inst: U, args: [...A]): R
    apply(inst: T, args: [...A]): R
}

function apply(reducer: (initial: number) => number): (number[]) => number {
    reducer.apply(undefined, [0, []])
}

const func = apply(foldl<number>((x, y) => x + y))

func([1, 2, 3]) // Runtime error

Saya akan menambahkan variasi saya juga. Mari kita lihat contoh kari variadic dari proposal:

function curry<...T,...U,V>(f: (...ts: [...T, ...U]) => V, ...as:...T): (...bs:...U) => V {
    return ...b => f(...as, ...b);
}

Jadi, saya mulai menggunakannya:

function f(a: number, b: string, c: string) { return c.toUpperCase(); }
var a: [number, string] = [1, "boo", 2]; // valid
const cf = curry(f, ...a); // cf is of type string => string
cf("a"); // runtime error

@isiahmeadows Apakah mereka direpresentasikan sebagai parameter tipe istirahat atau sebagai tipe Tuple, sepertinya Anda keberatan dengan kemampuan untuk menyebarkannya dalam posisi Tuple.

@Igorbek Saya pikir contoh Anda serupa karena masalahnya bukan bagaimana urutan tipe variadik diwakili. Ini adalah kemampuan untuk menyebarkannya dalam tupel yang mengarah ke masalah.

@JsonFreeman Lebih dari itu saya keberatan dengan perilaku ini:

class A {}
class B {}
class C {}

declare function foo(a: A, b: B): C;

// This should not work
let value: [A, B, C]
foo(...value)

Apakah itu memperjelas?

@isiahmeadows itu harus bekerja sebenarnya

@JsonFreeman
Saya merasa tidak seharusnya. Itu keberatan terbesar saya. Saya merasa itu berpotensi berbahaya jika memang demikian.

Pertanyaan: apa yang seharusnya menjadi tipe pengembalian yang disimpulkan dari ret ?

declare function foo(a: A, b: B, c: C, d: D): D
let ret = foo.bind(...[new A(), new B(), new D()])

Ini sebenarnya cukup penting.

Contoh terakhir itu sepertinya tidak akan berhasil. Pada dasarnya Anda memerlukan mekanisme untuk menyelaraskan urutan tipe jika function.bind benar-benar akan berfungsi dengan baik. Anda akan membutuhkan sesuatu yang mirip dengan unifikasi, di mana jenis argumen untuk mengikat dicocokkan dengan argumen fungsi asli, dan kemudian sisanya dalam tipe kembalian.

Yang mengatakan, sepertinya tidak ada apa pun dalam apa yang telah diusulkan atau dibahas dapat menangani itu (terlepas dari apakah argumen tambahan tupel diizinkan), meskipun mungkin saya melewatkan sesuatu.

Saya pikir masalah terbesar adalah bahwa semacam pencocokan pola Tuple, di mana setiap jenis parameter dicocokkan dengan jenis penyebaran, perlu dilakukan dengan parameter jenis (ala argumen LiveScript/CoffeeScript) untuk memperbaiki masalah itu. Mungkin tidak mungkin sebaliknya. Dan untuk seberapa rumitnya, selamat mengimplementasikannya. :senyum:

@JsonFreeman

Atau lebih tepatnya, itu akan membutuhkan pemeriksaan tipe yang tidak ketat (dalam arti bersemangat vs malas) untuk bekerja. Saya juga berpikir itu mungkin ekstensi yang lebih berguna daripada hanya tipe variadic, karena itu cukup banyak membuka pintu untuk banyak hal lain yang lebih berguna, seperti tipe rekursif diri.

// I hate this idiom.
interface NestedArray<T> extends Array<Nested<T>> {}
type Nested<T> = T | NestedArray<T>

// I would much prefer this, but it requires non-strict type checking.
type Nested<T> = T | Nested<T>[]

Untungnya, pemeriksaan tipe non-ketat harus menjadi perubahan murni yang tidak melanggar satu-satunya kode yang sebelumnya gagal diperiksa sekarang berfungsi.

Itu mungkin hal terbesar yang menghalangi pengetikan yang benar Function.prototype.bind , selain fakta itu akan membutuhkan tanda tangan tipe yang sangat kompleks.

Itu hubungan yang menarik. Saya tidak yakin mereka terkait sekalipun. Masalah tipe rekursif adalah konsekuensi dari kebijakan caching untuk obat generik, dan representasi alias tipe dalam kompiler. Semua informasi ada di sana, hanya desain kompiler yang menghalangi.

Untuk pencocokan pola tupel, Anda tidak selalu dapat mengetahui berapa banyak argumen yang dicocokkan dengan tupel. Jika Anda menyebarkan array ke dalam argumen bind , Anda tidak tahu berapa banyak yang tersisa dalam panggilan balik yang dihasilkan.

@JsonFreeman yang sedang berkata, apakah menurut Anda sebagai langkah adopsi argumen penyebaran proposal operator #6229 perlu dipertimbangkan terlebih dahulu?

@JsonFreeman

Dan pemeriksaan tipe yang tidak ketat akan memungkinkan kemalasan yang cukup untuk membuatnya lebih mudah untuk memperbaiki masalah itu dengan Function.prototype.bind . Dengan kemalasan seperti itu, Anda dapat mencapai tipe itu dengan yang berikut (yang akan membutuhkan sintaks Tuple untuk mengurutkannya, kecuali beberapa parameter istirahat tidak apa-apa dalam deklarasi tipe):

interface Function {
    bind<R, T, ...X, ...Y>(
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

Mengapa ini memerlukan pemeriksaan tipe yang tidak ketat untuk menyimpulkan? Anda harus menyimpulkan jenis sisanya langkah demi langkah untuk memeriksa fungsinya. Mengingat hal berikut, inilah cara memeriksanya:

// Values
declare function func(a: number, b: string, c: boolean, d?: symbol): number

let f = func.bind(null, 1, "foo")

// How to infer
bind<R, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => R,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => R

// Infer first type parameter
bind<number, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => number,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer second type parameter
bind<number, any, ...X, ...Y>(
    this: (this: any, ...args: [...X, ...Y]) => number,
    thisObject: any,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer first part of rest parameter
bind<number, any, number, ...*X, ...Y>(
    this: (this: any, ...args: [number, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, ...*X]
): (this: any, ...rest: [...Y]) => number

// Infer second part of rest parameter
bind<number, any, number, string, ...*X, ...Y>(
    this: (this: any, ...args: [number, string, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, string, ...*X]
): (this: any, ...rest: [...Y]) => number

// First rest parameter ends: all ones that only uses it are fully spread
bind<number, any, number, string, ...Y>(
    this: (this: any, ...args: [number, string, ...Y]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [...Y]) => number

// Infer first part of next rest parameter
bind<number, any, number, string, boolean, ...*Y>(
    this: (this: any, ...args: [number, string, boolean, ...*Y]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, ...*Y]) => number

// Infer second part of next rest parameter
// Note that information about optional parameters are retained.
bind<number, any, number, string, boolean, symbol?, ...*Y>(
    this: (
        this: any,
        ...args: [number, string, boolean, symbol?, ...*Y]
    ) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, symbol?, ...*Y]) => number

// Second rest parameter ends: all ones that only uses it are exhausted
bind<number, any, number, string, boolean, symbol?>(
    this: (this: any, ...args: [number, string, boolean, symbol?]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, symbol?]) => number

// All rest parameters that are tuples get converted to multiple regular
parameters
bind<number, any, number, string, boolean, symbol?>(
    this: (
        this: any,
        x0: number,
        x1: string,
        x2: boolean,
        x3?: symbol
    ) => number,
    thisObject: any,
    x0: number,
    x1: string
): (this: any, x0: boolean, x1?: symbol) => number

// And this checks

Beginilah cara kerja pemeriksaan tipe non-ketat. Itu menyimpulkan jenis sesuai kebutuhan, alih-alih instan melihatnya. Anda dapat (dan harus) menggabungkan dua lintasan, sehingga jenis yang salah gagal. Contoh:

let f = func.bind(null, 1, Symbol("oops"))

// How to infer
bind<R, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => R,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => R

// Infer first type parameter
bind<number, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => number,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer second type parameter
bind<number, any, ...X, ...Y>(
    this: (this: any, ...args: [...X, ...Y]) => number,
    thisObject: any,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer first part of rest parameter
bind<number, any, number, ...*X, ...Y>(
    this: (this: any, ...args: [number, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, ...*X]
): (this: any, ...rest: [...Y]) => number

// Infer second part of rest parameter
bind<number, any, number, string, ...*X, ...Y>(
    this: (this: any, ...args: [number, string, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, symbol /* expected string */, ...*X] // fail!
): (this: any, ...rest: [...Y]) => number

Dalam hal ini, parameter yang diharapkan harus menjadi yang pertama disimpulkan dalam putaran itu, melakukan iterasi mendalam-pertama. Dalam hal ini, yang pertama disimpulkan dalam pencarian itu adalah string, dan simbol tidak dapat ditetapkan ke string, jadi ini gagal dengan itu.


Dan karena ini dan mencoba mengetik Function.prototype.apply , pendapat saya tentang penggunaan tupel untuk menerapkan tipe istirahat telah berubah.

interface Function {
    apply<T, R, ...X>(
        this: (this: T, ...args: [...X]) => R,
        thisArg: T,
        args: [...X]
    ): R
}

Beberapa catatan lainnya:

  1. Perlu ada cara untuk menyebarkan array dan tupel sebagai parameter tipe istirahat.

ts interface Foo extends Function<void, ...string[]> {}

  1. Perlu ada dua tipe terpisah untuk konstruktor dan callable, dengan Fungsi menjadi gabungan keduanya. Objek yang dapat dipanggil harus mengimplementasikan antarmuka yang dapat dipanggil, konstruktor kelas harus mengimplementasikan antarmuka yang dapat dibangun, dan fungsi ES5 harus mengimplementasikan penyatuan keduanya.
  2. Function.prototype.bind dan teman-teman harus memeriksa semua kelebihan fungsi tersebut. Jika ada beberapa pekerjaan itu, itu harus mengembalikan gabungan dari semuanya.

Parameter tipe tersebut dalam contoh Anda sebenarnya bukan parameter tipe dari tanda tangan pengikat. Mereka termasuk dalam tipe Fungsi. Tapi ya, idenya adalah jika Anda dapat menggunakan dua parameter istirahat, atau menyebarkan dua parameter tipe istirahat dalam sebuah Tuple, Anda akan dapat menulis ini.

Agar tanda tangan dari bind cukup fleksibel, batas antara ...X dan ...Y perlu diputuskan berdasarkan basis per panggilan. Itu perlu disimpulkan. Akan menjadi masalah jika tanda tangan menggunakan ...X secara terpisah. Dalam hal ini, batas tidak akan diputuskan. Sebagai contoh:

interface SomeType<T, R, ...X, ...Y> {
     someMethod(someArgs): [...X]; // No way of knowing how long X is 
}

Dan kelebihan beban cukup menjadi masalah untuk tipe Fungsi. Saya tidak berpikir Anda ingin mengambil elemen tipe serikat pada setiap argumen, karena itu akan memungkinkan pencampuran dan pencocokan parameter antara kelebihan beban. Apakah itu yang kamu maksud?

@JsonFreeman

_TL;DR: Lewati ke jeda garis horizontal. Saya punya ide baru yang lebih praktis._

  1. Ya, saya sadar mereka benar-benar milik Function itu sendiri.
  2. Masalah semacam itu adalah mengapa saya mengatakan pencocokan tipe yang tidak ketat (dalam arti Haskell) diperlukan. Anda tidak dapat dengan bersemangat menyelesaikan jenisnya secara normal, karena yang itu akan membutuhkan pencarian yang berulang dan malas untuk dilakukan. Dimungkinkan untuk menentukan secara algoritmik, tetapi Anda harus melacak hal-hal yang biasanya tidak perlu dilacak seperti, C++.
  3. Jika dua argumen berada dalam isolasi (seperti dalam contoh Anda), kompiler harus mengeluh. Dan situasi itu dapat dideteksi dengan analisis ketergantungan tingkat tipe dari setiap argumen variadik di antarmuka/apa pun. Itu juga tidak sepele, tetapi dapat diverifikasi ketika membaca deklarasi tipe itu sendiri (sebenarnya, tidak lama setelahnya).

Meskipun saya juga berpikir mungkin sedikit lebih layak untuk mendefinisikan situasi semacam ini hanya pada metode yang dimaksud. Ini juga akan jauh lebih mudah dan lebih cepat untuk mendeteksi jenis masalah potensial yang Anda singgung.

interface Function<R, T, ...A> {
    // Split it up for just this method, since it's being resolved relative to the
    // method itself.
    bind[...A = ...X, ...Y](
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

Ada potensi masalah lain yang akan jauh lebih sulit untuk diselesaikan (dan mengapa saya pikir itu harus dibatasi menjadi 2, bukan _n _ divisi):

declare function foo<...T>[...T = ...A, ...B, ...C](
    a: [...A, ...C],
    b: [...A, ...B],
    c: [...B, ...C]
): any

// This should obviously check, but it's non-trivial to figure that out.
let x = foo<
    boolean, number, // ...A
    string, symbol,  // ...B
    Object, any[]  // ...C
>(
    [true, 1, {}, []],
    [true, 1, "hi", Symbol()],
    ["hi", Symbol(), {}, []]
)

_Maaf jika saya terlalu mendalami teori CS di sini..._

Ya, saya pikir itu ide yang tepat. Itu tidak cantik, tapi saya tidak bisa memikirkan cara lain untuk mengetik dengan benar bind , mengetahui argumen tipe Function . Hal utama adalah bahwa batas harus disimpulkan. Dan saya setuju bahwa itu harus dibatasi hingga 2 ember sehingga Anda harus menyimpulkan 1 batas, alih-alih sejumlah batas yang sewenang-wenang, yang dapat meledak secara kombinatorial.

Kemungkinan ada lebih banyak masalah yang belum kita pikirkan.

@JsonFreeman Masalah lain adalah hal-hal seperti curry . Saya belum menemukan sesuatu yang dapat mengetik itu dengan benar. Dan itu akan memakan waktu cukup lama sebelum saya bisa. Saya harus melakukan peretasan tipe Haskell yang serius untuk menghasilkan proses seperti itu.

Memikirkan bagaimana jenis proposal dapat bekerja dengan beberapa fungsi Bluebird.

interface PromiseConstructor {
    // all same type
    all<T>(promises: PromiseLike<T>[]):  Promise<T[]>;
    join<T>(...promises: PromiseLike<T>[]):  Promise<T[]>;
    // varying types
    all<...T>(promises: [...PromiseLike<T>]): Promise<[...T]>;
    join<...T>(...promises: [...PromiseLike<T>]): Promise<[...T]>;
    // this is sketchy...    ^
}

interface Promise<T> {
    // all same type
    then<U>(onFulfill: (values: T) => U): Promise<U>;
    spread<U>(onFulfill: (...values: T) => U): Promise<U>;
}
interface Promise<...T> {
    // varying types
    then<U>(onFulfill: (values: [...T]) => U): Promise<U>;
    spread<U>(onFulfill: (...values: [...T]) => U): Promise<U>;
}

Apakah kami punya solusi untuk all<...T>(promises: [...PromiseLike<T>]): Promise<...T>; atas?

@DerFlatulator

Lihat komentar besar saya di PromiseConstructor. Saya juga mengoreksi antarmuka Promise Anda menjadi sedikit lebih dekat dengan proposal saya.

interface PromiseConstructor {
    new <T>(callback: (
        resolve:
        (thenableOrResult?: T | PromiseLike<T>) => void,
        reject: (error: any) => void
    ) => void): Promise<T, [T]>;
    new <...T>(callback: (
        resolve:
        (thenableOrResult?: [...T] | PromiseLike<[...T]>) => void,
        reject: (error: any) => void
    ) => void): Promise<[...T], ...T>;

    // all same type
    all<T>(promises: PromiseLike<T>[]):  Promise<T[], ...T[]>;
    join<T>(...promises: PromiseLike<T>[]):  Promise<T[], ...T[]>;

    // varying types
    all<...T>(promises: [...PromiseLike<T>]): Promise<[...T], ...T>;
    join<...T>(...promises: [...PromiseLike<T>]): Promise<[...T], ...T>;

    // all<...T>(promises: [...PromiseLike<T>]): Promise<[...T], ...T> should
    // expand to this:
    //
    // all<T1, T2, /* ... */>(promises: [
    //     PromiseLike<T1>,
    //     PromiseLike<T2>,
    //     /* ... */
    // ]): Promise<[T1, T2, /* ... */], T1, T2, /* ... */>;
    //
    // This should hold for all rest parameters, potentially expanding
    // exponentially like ...Promise<[Set<T>], ...Thenable<T>> which should
    // expand to something like this:
    //
    // Promise<[Set<T1>], Thenable<T1>, Thenable<T2> /* ... */>,
    // Promise<[Set<T2>], Thenable<T1>, Thenable<T2> /* ... */>,
    // // etc...
}

interface Promise<T, ...U> {
    // all same type
    then<V>(onFulfill: (values: T) => V): Promise<[V], V>;
    spread<V>(onFulfill: (...values: T) => V): Promise<[V], V>;

    // all same type, returns tuple
    then<...V>(onFulfill: (values: T) => [...V]): Promise<[...V], ...V>;
    spread<...V>(onFulfill: (...values: T) => [...V]): Promise<[...V], ...V>;

    // varying types
    then<V>(onFulfill: (values: [...U]) => V): Promise<[V], V>;
    spread<V>(onFulfill: (...values: [...U]) => V): Promise<[V], V>;

    // varying types, returns tuple
    then<...V>(onFulfill: (values: [...U]) => [...V]): Promise<[V], ...V>;
    spread<...V>(onFulfill: (...values: [...U]) => [...V]): Promise<[V], ...V>;
}

Jika [...Foo<T>] diperluas ke [Foo<T1>, Foo<T2>, /*... Foo<TN>*/] , lalu apakah [...Foo<T,U>] merupakan kesalahan sintaks atau ekspansi kombinatorial?

@DerFlatulator

  1. Jika tepat salah satu dari T atau U adalah parameter istirahat, ia akan mengembang secara normal. Dengan asumsi T adalah parameter istirahat, maka itu akan menjadi [Foo<T1, U>, Foo<T2, U>, /*... Foo<TN, U>*/] .
  2. Jika keduanya adalah parameter diam, dan panjangnya dapat disimpulkan dengan benar, itu harus menjadi ekspansi kombinatorial (well...panjang T kali panjang U).
  3. Jika keduanya bukan parameter istirahat, ini adalah kesalahan sintaks.

Perhatikan bahwa saya sangat menentang lebih dari 2 parameter istirahat untuk alasan praktis, dan bahwa parameter lainnya, jika perlu dipisah, hanya boleh dibagi berdasarkan metode per. Sesuatu seperti ini:

interface Function<R, T, ...A> {
    // Split it up for just this method, since it's being resolved relative to the
    // method itself.
    bind[...A = ...X, ...Y](
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

_(Jika seseorang dapat membuat sintaks yang lebih baik, saya setuju. Saya tidak menyukainya, tetapi saya tidak dapat menemukan apa pun yang tidak bertentangan secara visual.)_

@isiahmeadows

Dengan 2., dalam urutan apa ekspansi itu?

[
Foo<T1, U1>, Foo<T2, U1>, /*... */ Foo<TN,U1>,
Foo<T1, U2>, Foo<T2, U2>, /*... */ Foo<TN,U2>,
/* ... */
Foo<T1, UN>, Foo<T2, UN>, /*... */ Foo<TN,UN>
]

Atau sebaliknya:

[
Foo<T1, U1>, Foo<T1, U2>, /*... */ Foo<T1,UN>,
Foo<T2, U1>, Foo<T2, U2>, /*... */ Foo<T2,UN>,
/* ... */
Foo<TN, U1>, Foo<TN, U2>, /*... */ Foo<TN,UN>
]

Bukankah ambiguitas ini akan menyebabkan kebingungan? Mungkin membatasi pada satu dimensi adalah bijaksana.


Hanya saran alternatif untuk sintaks split:

interface Function<R, T, ...A> {
    bind<[...X, ...Y] = [...A]>(
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

@DerFlatulator

Saya mengharapkan yang kedua. Dan saya ragu itu akan menyebabkan terlalu banyak kebingungan, karena selama itu konsisten, orang akan cepat terbiasa. Ini juga merupakan kasus tepi yang tidak biasa yang hanya akan benar-benar dijalankan dalam praktik oleh orang-orang yang tahu apa yang mereka lakukan, atau orang-orang yang harus mempertanyakan kebutuhannya sejak awal.

Saya juga melihatnya saat Anda memperluas yang pertama, lalu yang kedua untuk setiap bagian dari yang pertama. Seperti kodesemu ini:

for (let TT of T) {
  for (let UU of U) {
    expand(TT, UU);
  }
}

Iterasi pada beberapa ide di atas...

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TNewThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>
}

Di sini, [...TBound, ...TUnbound] = [...TArgs] valid karena panjang ...TBound diketahui dari panjang args . Itu juga mengizinkan perubahan tipe TThis .

Satu masalah dengan pendekatan ini adalah Anda hanya dapat mengikat this sekali, misalnya:

interface IFoo { a: number }
interface IBar extends IFoo { b: boolean }
function f(a: number) { }

let x = f.bind(<IBar>{ a: 1, b: false }, 2); // inferred type: Function<number, IBar>
let y = x.bind(<IFoo>{ a: 1 }) // inferred type: Function<number, IFoo>

Jenis kesimpulan y salah, seharusnya Function<number, IBar> . Saya tidak yakin apakah ini menjadi perhatian atau tidak, tetapi pemecahannya akan membutuhkan pengenalan logika ke dalam sintaks <T> .

Pilihan 1

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TNewThis = TThis is undefined ? TNewThis : TThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>;
}

pilihan 2

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TThis is undefined,
        TNewThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>;

    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TThis is defined
    >(
        thisObject: any,
        ...args: [...TBound]
    ): Function<TReturn, TThis, ...TUnbound>;
}

Namun hal itu kemungkinan berada di luar cakupan proposal ini.

Saya tidak berpikir kita harus mengizinkan ekspansi semacam itu dengan menggunakan operator tipe spread. Saya menganggap operator spread sebagai "penghilang tanda kurung" yang benar-benar selaras dengan operator spread array dan operator spread objek/properti (proposal tahap 2). Bandingkan saja:

let a =        [1, 2];
let b = [0, ...a     , 3];
//      [0, ...[1, 2], 3]
//      [0,     1, 2 , 3]  // removed brackets

let c =               { a: 1, b: "b" };
let d = { e: true, ...c               , f: 3 };
//      { e: true, ...{ a: 1, b: "b" }, f: 3 };
//      { e: true,      a: 1, b: "b"  , f: 3 };

Anda menyarankan untuk memperluasnya untuk membangun set tipe baru:

<...T> = <A, B, C>
...U<T> = <U<A>, U<B>, U<C>>

Ini operasi yang sama sekali berbeda. Jika Anda mau, itu dapat dimodelkan dengan konstruksi tingkat tinggi seperti:

<...(from R in T select U<R>)> // linq-like
<...(T[R] -> U<R>)> // ugly

@Igorbek Bagaimana dengan menggunakan operator untuk menentukan apa yang akan diperluas?

interface PromiseConstructor {
    all<
      ...T, 
      [...TThen] = ...(PromiseLike<@T> | @T)
    >(
      promises: [...TThen]
    ): Promise<[...T], ...T>;
}

Di mana ...Foo<<strong i="9">@T</strong>, U> berkembang menjadi [Foo<T1,U>, /*...*/, Foo<TN,U>] .

...(PromiseLike<@T> | @T) diperluas ke
[PromiseLike<T1>|T1, /*...*/, PromiseLike<TN>|TN]

Beberapa alternatif sintaks:

  • ...Foo<&T,U>
  • (T) Foo<T,U>
  • (...T => Foo<T,U>)
  • for (T of ...T) Foo<T,U>

Saya setuju dengan @Igorbek di sini. Setidaknya pada tahap ini, pemetaan urutan tipe tidak tampak seperti prioritas, mengingat kita masih mencoba untuk memecahkan masalah yang lebih mendasar dari parameter tipe variadik.

Saya tidak punya banyak masalah dengan melarangnya (setidaknya pada awalnya), karena perilaku untuk itu sangat tidak masuk akal, dan dua orang yang berbeda mungkin mengharapkan dua hal yang sangat berbeda, bahkan. Saya setuju dengan @Igorbek setidaknya untuk saat ini, karena TypeScript pertama-tama harus memiliki model tipe tingkat tinggi (itu map ping tipe dalam arti tertentu). Dan jenis pesanan yang lebih tinggi bukanlah sesuatu yang bisa Anda gunakan begitu saja.

Jadi pasti :+1: untuk pelarangan itu, mungkin cukup lama. Meskipun bagus untuk dimiliki, sangat rumit untuk diterapkan, dan akan menjadi peretasan yang lengkap untuk dilakukan, karena TypeScript tidak menggunakan sistem tipe yang fungsional dan aman.

Datang agak terlambat, tapi saya setuju dengan @Igorbek juga. Mengulangi komentar saya yang dibuat di #1336 dan meminjam ide dari pengemasan parameter C++ yang memiliki operator 'paket' dan 'bongkar' yang jelas.

Jenis pengepakan ke dalam Tuple tampaknya konsisten dengan penggunaan TypeScript dari operator spread:

let [x, y, ...rest] = [1, 2, 3, 4, 5] // pack
foo(...params) // unpack
let all = [1, 2, ...other, 5] // unpack

// keep in mind this is already implemented, which kind of similar to mapping types
function map(arr) { ... }
let spreadingmap = [1, 2, ...map(other), 5];

Yang membuat <...T_values> = [T1, T2, T3, etc...] jauh lebih mudah untuk dipikirkan.

Sementara C++ menggunakan operator spread untuk pengemasan dan elipsis untuk membongkar, menggunakan spread untuk keduanya lebih konsisten dengan TypeScript.

module Promise {
  function all<...T_values>(   // pack into a tuple of types, conceptually identical to rest parameters
      values: [ (<PromiseLike<T*>> ...T_values) ]  // unpack, cast, then repack to tuple
  ): Promise<T_values> // keep it packed since T_values is a tuple of whatever types
}

@isiahmeadows @JsonFreeman apa gunanya semua ini tanpa pemetaan?

Juga seperti yang diangkat di #1336, bagaimana dengan variadic Array.flatten ?

@jameskeane Babak pertama itu adalah ide awal, tetapi itu tidak mencakup kasus parameter istirahat tengah (yang dimiliki beberapa API):

function foo<...T>(a: Foo, b: Bar, ...rest: [...T, Baz]): Foo;

Itu juga tidak mencakup Function.prototype.apply vs Function.prototype.call sangat baik.

Adapun #1336, mungkin juga diterapkan melalui ini:

angular.module('app').controller(['$scope', function($scope: ng.IScope) { /*etc...*/ }]);

interface IModule {
  controller(injectable: [...string[], () => any]);
}

Saya telah menyusul, dan menyadari bahwa saya secara naif berasumsi bahwa tipe Tuple memiliki panjang yang ketat; imo mana yang paling intuitif. Jadi dengan asumsi kita mendapatkan tipe tupel panjang yang ketat (#6229), apa masalahnya?

@isiahmeadows Dalam contoh Anda di atas dari kasus parameter istirahat tengah, apakah itu tidak diselesaikan dengan memiliki tupel panjang yang ketat? Saya membaca ...rest: [...T, Baz] sama dengan spread unpacking arr = [...other, 123] . Ini adalah masalah yang sama yang Anda angkat dengan curry , bukan?

Adapun apply dan call , apakah mereka tidak tercakup oleh tipe yang berpotongan? (Bukannya saya benar-benar melihat nilai memiliki tipe pada antarmuka Function ).

// as in
const t: [any, string] & [number, any] = [1, "foo"]

interface Function<R, T, ...A> {
    bind<...Y, ...Z>(
        this: (this: T, ...args: A & [...Y, ...Z]) => R, // tricky bit, luckily intersecting tuples is pretty easy
        thisObject: T,
        ...args: Y
    ): (this: any, ...rest: Z) => R
}

@jameskeane

Proposal variadik saat ini mengasumsikan #6229 benar-benar akhirnya diterima (yaitu tupel ketat secara default).

Adapun func.apply , func.bind , func.call , dan _.curry , satu-satunya masalah adalah dengan func.bind , _.curry , dan teman-teman, atau lebih umum apa pun menggunakan aplikasi parsial. Anda juga harus dapat memilih parameter istirahat mana yang akan dipisahkan, dan itu hanya dapat benar-benar dilakukan pada basis per metode.

call dan apply cukup mudah:

type Callable<R, T, ...A> = (this: T, ...args: [...A]) => R;

interface Function<R, T, ...A> {
    call(this: Callable<R, T, ...A>, thisArg: T, ...args: [...A]): R;
    apply(this: Callable<R, T, ...A>, thisArg: T, args: [...A]): R;
}

bind akan lebih sulit. Parameter pemisahan harus dicocokkan sesuai kebutuhan, tidak seperti yang diinginkan, seperti yang terjadi sekarang, sampai paruh pertama sepenuhnya dibongkar. Ini harus diimplementasikan sebagai sintaks, sehingga kompiler dapat membedakannya dan membedakan jenisnya dengan benar tanpa mengevaluasi apa pun.

// Function.prototype.bind
type Callable<R, T, ...A> = (this: T, ...args: [...A]) => R;
type Constructible<R, ...A> = new (...args: [...A]) => R;

interface Function<R, T, ...A> {
    // my proposed syntax for splitting a rest parameter
    bind[[...A] = [...X, ...Y]](
        this: Callable<R, T, ...A>
        thisArg: T,
        ...args: [...X]
    ): Callable<R, any, ...Y>;

    bind[[...A] = [...X, ...Y]](
        this: Constructible<R, ...A>
        thisArg: T,
        ...args: [...X]
    ): Constructible<R, ...Y>;

    bind[[...A] = [...X, ...Y]](
        this: Callable<R, T, ...A> & Constructible<R, ...A>
        thisArg: T,
        ...args: [...X]
    ): Callable<R, T, ...Y> & Constructible<R, ...Y>;
}

curry akan sangat sulit, karena harus diketahui bahwa f(1, 2, 3) === f(1, 2)(3) === f(1)(2, 3) === f(1)(2)(3) . Tidak hanya harus ada kemampuan untuk membagi parameter istirahat menjadi dua seperti di bind , harus ada kemampuan untuk melakukan pencocokan pola yang sangat primitif pada basis per metode.

interface Curried<R, T, ...XS> {
    // none passed
    (): this;

    // all passed
    (this: T, ...args: [...XS]): R;
}

interface CurriedMany<R, T, X, ...YS> extends Curried<R, T, X, ...YS>  {
    // penultimate case, constraint that ...YS contains no parameters
    [[...YS] = []](arg: X): Curried<R, T, X>;

    // otherwise, split rest into ...AS and ...BS, with `A` used as the pivot
    // (basically, default case)
    [[...YS] = [...AS, A, ...BS]](
        ...args: [X, ...AS]
    ): CurriedMany<R, T, A, ...BS>;
}

function curry<R, T>(f: (this: T) => R): (this: T) => R;
function curry<R, T, X>(f: (this: T, arg: X) => R): Curried<R, T, A>;
function curry<R, T, X, ...YS>(
    f: (this: T, arg: X, ...args: [...YS]) => R
): CurriedMany<R, T, X, ...YS>;

Saya tidak percaya penambahan untuk curry akan menyelesaikan Turing, tetapi itu akan mendekati. Saya pikir hal utama yang mencegahnya adalah kemampuan untuk mencocokkan spesialisasi tipe tertentu (yang dimiliki C++, Scala, dan Haskell, tiga bahasa dengan sistem tipe lengkap Turing).

@sandersn Saya tidak dapat melihat contoh di atas, tetapi dapatkah saya bertanya tentang batasan pada parameter variadik?

Perhatikan contoh berikut:

interface HasKey<T> {
    Key(): T;
}

class Row<...T extends HasKey<X>, X> {
    // ...
}

_Kebetulan, lihat https://github.com/Microsoft/TypeScript/issues/7848 untuk diskusi tentang kemungkinan menjatuhkan persyaratan bahwa X perlu dicantumkan_

Sekarang ada kemungkinan beberapa ambiguitas di sini, apakah kendalanya adalah:

  1. (...T) extends HasKey<X> atau
  2. ...(T extends HasKey<X>)

Dalam contoh ini saya mengasumsikan 2.

Apakah kendala semacam ini (1 dan/atau 2) mungkin terjadi?

@myitcv 2 mungkin akan menjadi rute terbaik untuk ditempuh, tetapi masuk akal untuk menggunakan kembali logika yang ada untuk memeriksa batasan.

Yah...Saya baru menyadari sesuatu: bagaimana array yang berisi tipe variadic? Atau lebih spesifik lagi, apa jenis arg bawah ini?

function processItems<...T>(...args: [...T]): void {
    for (const arg of args) { // Here
        process(arg);
    }
}

Saya kira Anda bertanya apa tipe elemen dari args . Untuk tupel, saya percaya ini biasanya jenis elemen gabungan. Saya tidak bisa memikirkan cara yang lebih baik untuk mengetik itu.

@sandersn dapatkah Anda mengomentari apa status fitur ini? Saya merasa sudah banyak diskusi, tetapi sepertinya belum ada rencana pasti untuk fitur tersebut, benarkah?

@JsonFreeman Saya bertanya secara spesifik apa arg itu. Menurut pendapat saya, seharusnya any untuk contoh asli saya, dan Item<T> dengan di bawah ini (dengan F-terbatas T):

function processItems<...T extends Item<T>>(...args: [...T]): void {
    for (const arg of args) { // Here
        process(arg);
    }
}

Ini agar jenisnya dapat diselesaikan secara lokal. Anda tidak mengetahui tipe sebelumnya, dan ini akan sangat mempercepat kompilasi, karena Anda tidak perlu menghitung tipe dalam fungsi untuk setiap panggilan ke sana. Perhatikan bahwa jika Anda hanya membutuhkan tipe argumen tunggal, typeof arg sudah cukup, dan kemungkinan akan lebih pendek.

Oh maaf, untuk contoh aslinya, maksud saya tipenya adalah T . Sebenarnya, untuk contoh kedua Anda, saya juga berpikir itu harus T.

Maksudku Item<any> di detik...Maaf.

Ketika saya mengatakan seharusnya T, saya berasumsi T adalah tipe, tapi saya rasa inti dari fitur ini adalah T bukan tipe (saya pikir). Jadi ya, saya kira seharusnya any dan Item<any> dalam contoh Anda.

Tetapi secara lebih luas, saya ingin tahu seberapa aktif tim mempertimbangkan fitur ini setelah 2.0. Saya tidak punya pendapat yang kuat, hanya bertanya-tanya.

Alasan saya tidak berpikir itu seharusnya T adalah karena Anda tidak tahu apa itu T . Kecuali, tentu saja, maksud Anda memiliki variadic T mewakili salah satu jenis dari daftar jenis variadic, atau ketika menyebar, daftar itu sendiri, yaitu T adalah subtipe dari semua argumen yang diteruskan ke argumen ...T , dan [...T] dapat ditetapkan ke T[] .

Atau, untuk memperjelas apa yang saya maksud dengan semua jargon yang tidak jelas, inilah yang saya maksud dari segi kode:

// To put it into code
function foo<...T>(list: [...T]): void {
    // This is allowed
    let xs: T[] = list

    // This is allowed
    let list2: [...T] = list

    // This is not allowed
    let list1: [...T] = xs

    // This is allowed
    let item: ?T = null

    // This is not allowed, since it's not immediately initialized
    let other: T

    for (let arg of args) {
        // This is allowed
        let alias: T = arg

        // This is allowed
        let other: ?T = arg

        // This is allowed, since `item` is defined upwards as `?T`
        item = arg

        // This is allowed, since you're doing an unsafe cast from `?T` to `T`.
        alias = item as T
    }
}

Itu mungkin akan lebih masuk akal, dan itu akan jauh lebih fleksibel.

Itu masih ada dalam daftar yang bagus untuk dimiliki, tetapi ini terutama menarik bagi penulis perpustakaan dan memiliki solusi yang layak — _n_ kelebihan — jadi saya tidak secara aktif mengerjakannya. Jika saya harus menebak, saya akan mengatakan 2.1 mungkin tetapi tidak mungkin.

Jika/ketika kita berkomitmen untuk mendukung objek rest/spread dengan benar (#2103) maka jenis variadic mungkin cukup dekat untuk menyebarkan jenis untuk membenarkan melakukan semuanya sekaligus. (Jenis spread adalah varian dari tipe objek yang terlihat seperti { ...T, x: number, ...U, y: string, ...V } .)

Hanya ingin menyebutkan bahwa solusi n overloads tidak berfungsi untuk kelas atau antarmuka, yang merupakan minat khusus saya pada fitur ini.

@sandersn Apakah permintaan tarik untuk "_n_ overloads" akan diundang untuk fungsi bind , apply dan call , menggunakan this mengetik? Saya pikir itu akan menjadi kompromi sementara yang dapat diterima bagi banyak orang, dan dapat menangkap beberapa bug dalam proses untuk beberapa proyek.

@isiahmeadows

Alasan saya tidak berpikir itu harus T adalah karena Anda tidak tahu apa itu T.

Tampaknya bagi saya ada kesepakatan bahwa T adalah tipe tuple dari tipe variadik. Dalam contoh asli Anda, tipe arg akan sama dengan tipe elemen Tuple (seperti yang dicatat oleh @JsonFreeman , "tipe gabungan elemen"): Untuk sekarang bayangkan TypeScript mendukung menggunakan Tuple sebagai istirahat ketik (#5331).

function processItems<...T>(...args: T): void {
  for (const arg of args) { // Here - arg:number|string|boolean
    const other: ??? = arg; // I think the issue is, how to _represent_ this type?
  }
}
processItems(1, 'foo', false); // T is tuple [number, string, boolean]

Terpisah dari proposal ini, saya pikir harus ada cara untuk mewakili 'tipe elemen' dari sebuah Tuple. Itu bisa menjadi kegunaan lain untuk spread, yaitu seperti di atas ...T :: number|string|boolean ; bahwa menyebarkan tipe Tuple menghasilkan tipe elemennya.

for (const arg of args) {
  const cst: ...T = arg;
}

// also, even without variadic types...
type Record = [number, string];
function foo(args: Record) {
  for (const arg in args) {
    const cst: ...Record = arg;
  }
}

Dengan mengingat hal ini, contoh Anda yang lain:

function foo<...T>(...list: T): void {
  let xs: T[] = [list, list] // array of the variadic tuple type

  // This is allowed
  let list5: (...T)[] = [...list]

  // This is *not* allowed
  let list2: [...T] = list

  // This is not allowed
  let list1: [...T] = xs

  // This **is** allowed
  // single element tuple, of variadic union
  // i.e. with number|string|boolean
  //      list4 = [1] or list4 = ['foo'] or list4 = [false]
  let list4: [...T] = [list[n]]

  // This **is**  allowed
  let other: T;

  // This is allowed
  let another: ...T;

  for (let arg of args) {
    another = arg; // allowed, if spreading the tuple is the union type

  }
}

Tidak melupakan tujuan awal saya, saya menginginkan Promise.all diketik dengan kuat ...

declare module Promise {
  function all<...T>(promises: Promise<...T>[]): T; // means promises is an array of promises to the union type, not what I wanted.

  // Then we need something like, which is now very confusing
  function all<...T>(promises: [...Promise<T*>]): T; 
}}

@sandersn Sekarang fitur lain yang diminta mulai bergantung pada ini, dapatkah prioritasnya ditingkatkan? bind , call dll. pengetikan tergantung pada ini, dan sintaks ES bind jika/ketika keluar tergantung pada itu, jadi sekarang ada lebih banyak yang menunggangi ini daripada penulis perpustakaan unik yang mengganggu Anda sepanjang waktu . :)

Bukannya ini sangat konstruktif untuk ditambahkan, tetapi saya akan sangat senang jika kedua fitur ini berhasil menjadi 2.1. Saya tahu setidaknya satu perpustakaan (RxJS), di mana basis kode itu sendiri tidak akan ditingkatkan oleh fitur-fitur ini, mengkonsumsi kode juga akan jauh lebih tidak canggung dan rentan terhadap bug (setiap orang ketiga yang memulai dengan Angular 2 digigit oleh impor yang hilang untuk operator yang ditambal ke prototipe yang dapat diamati). Ini benar-benar akan menjadi fitur terobosan bagi orang yang ingin menulis kode fungsional yang dapat dipelihara.

Bisakah ini digunakan untuk memberikan definisi tipe lengkap untuk _.extend , yang tipe pengembaliannya adalah persimpangan dari semua parameternya?

declare module underscore {
  function extend<A, B, C, D, ...>(a: A, b: B, c: C, d: D, ...): A&B&C&D&...;
}

Tidak seperti berdiri. Perlu perpanjangan proposal yang memberikan detail tentang operator baru untuk jenis variadik — mungkin disebut &. @kitsonk mengusulkan operator ini sebelumnya, dalam komentar ini .
Saat ini fitur ini berada di bawah beberapa hal lain yang lebih penting, jadi saya sudah lama tidak melihat proposal ini.

Meskipun tidak memberikan jenis variadik penuh, #10727 adalah bagian dari solusi (dan kemungkinan akan mengatasi tantangan yang kita (@dojo) miliki).

Senang mendengarnya! Meskipun sebenarnya masih bukan jenis yang variadik. :( Misalnya, minggu ini ketika saya mencoba mengetik Object.assign , saya sampai sejauh ini:

interface Object {
  // binary version
  assign<T,U>(target: T, source: U): { ...T, ...U };
  // variadic version: bind a variadic kind variable ...T
  // and then spread it using SIX dots
  assign<...T>(...targets: ...T): { ......T };
}

Perhatikan bahwa sintaks "enam titik" adalah penyebaran objek dari variabel jenis Tuple, yang belum benar-benar kita bahas di atas.

@sandersn

Dengan Object.assign khususnya, itu dapat diketik dengan cara ini, dan secara teknis menangkap subset (walaupun agak terlalu lemah), karena targetnya bermutasi (Anda harus memiliki titik referensi untuk ini):

assign<T>(target: T, ...sources: Partial<T>[]): T;

Kesalahannya adalah ia mengubah targetnya, mengubah tipe strukturalnya di tempatnya.

@isiahmeadows maka inferensi akan memperbaiki T menjadi tipe target tanpa tipe akuntansi sources . Anda dapat mencobanya sekarang dengan versi non-variadik:

declare function _assign<T>(target: T, source: Partial<T>): T;
_assign({}, { a: 10 }); // T is {}

Seperti yang telah disebutkan, assign menggunakan _a spread type_ #10727 dan dapat didefinisikan dengan cara ini:

// non variadic
declare const assign: {
  <T>(target: T): T;
  <T, S>(target: T, source: S): {...T, ...S};
  <T, S1, S2>(target: T, source1: S1, source2: S2): {...T, ...S1, ...S2};
};
// variadic
declare function assign<T, [...S]>(target: T, ...sources: [...S]): {...T, ...[...S]};

_Catatan: Saya masih bersikeras pada sintaks berbasis Tuple [...T] yang lebih masuk akal bagi saya._

@sandersn BTW, apakah ada pembaruan kapan jenis variadic akan mendarat? Apakah ada kesempatan untuk melihatnya di 2.2?
Dan, mengenai sintaks, apakah Anda masih menerima umpan balik tentang sintaks atau Anda semua setuju dengan itu?

Sintaks dan semantik tingkat rendah belum memiliki konsensus yang jelas.

Pada Selasa, 13 Desember 2016, 13:26 Igor Oleinikov [email protected] menulis:

@sandersn https://github.com/sandersn BTW, apakah ada pembaruan kapan
jenis variadic akan mendarat? Apakah ada kesempatan untuk melihatnya di 2.2?
Dan, mengenai sintaks, apakah Anda masih menerima umpan balik tentang sintaks atau
Anda semua setuju dengan itu?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-266819647 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AERrBIa5fE8PSk-33w3ToFqHD9MCFoRWks5rHuM5gaJpZM4GYYfH
.

Adakah ide tentang apa status masalah ini?

jadi apa pilihan yang Anda pikirkan? apakah ini ada dalam agenda dengan tim? itu satu-satunya bagian lemah dari sistem tipe yang berulang kali saya temui. saya punya dua kasus penggunaan. sederhana dan kompleks, tetapi lebih umum.

sederhana adalah menambahkan supertipe Tuple extends any[] yang hanya dapat disubtipekan oleh tipe Tuple. Karena spread harus berupa subtipe any[] , ini akan berhasil:

declare interface Plugin<A: Tuple, P> {
  (...args: A): P | Promise<P>
}

const p: Plugin<[string, { verbose: boolean }], int> =
  (dest, { verbose = false }) => 4

saat ini, ...args: T[] hanya diperbolehkan di akhir tanda tangan.

kasus penggunaan yang kompleks akan membutuhkan ...args: Tuple agar legal di mana saja di dalam tanda tangan (yang tidak masalah, karena tupel memiliki panjang tetap):

/**
 * Takes a function with callback and transforms it into one returning a promise
 * f(...args, cb: (err, ...data) => void) => void
 * becomes
 * g(...args) => Promise<[...data]>
 */
function promisify<A extends Tuple, D extends Tuple, E>
    (wrapped: (...args: A, cb: (error: E, ...data: D) => void) => void)
    : ((...args: A) => Promise<Data>) {
  return (...args) => new Promise((resolve, reject) =>
    wrapped(...args, (e, ...data) =>
      e ? reject(e) : resolve(data)))
}

const write: ((fd: number, string: string, position?: number, encoding?: string)
              => Promise<[number, string]>) =
  promisify(fs.write)

Ya, saya baru saja mulai dengan TypeScript kemarin dan ini sudah membuat tidak mungkin untuk mengetik fungsi saya secara otomatis (tentu saja saya masih bisa melakukannya secara manual) karena satu dekorator yang saya gunakan untuk membungkus fungsi saya (yang merupakan hal pertama Saya mencoba memulai dengan!):

function portable(func) {
    return function(...args) {
        if (this === undefined) {
            return func(...args)
        } else {
            return func(this, ...args)
        }
    }
}

Secara efektif semua yang dilakukan dekorator adalah membuat fungsi diizinkan untuk dipanggil sebagai metode juga sehingga mereka dapat dilampirkan sebagai metode dan bekerja secara identik, sebagai contoh dasar inilah contoh buruk yang menambal prototipe Array dengan versi dasar flatMap :

function _flatMap<T, R>(
    array: T[],
    iteratee: (item: T) => R[]
): R[] {
    let result: R[] = []
    for (const item of array) {
        for (const value of iteratee(item)) {
            result.push(value)
        }
    }
    return result
}

const flatMap = portable(_flatMap)
Array.prototype.flatMap = flatMap

flatMap([1,2,3,4], x => [x, x])
// Is the same as
[1,2,3,4].flatMap(x => [x, x])
// Is the same as
flatMap.apply([1,2,3,4], [x => [x, x]])
// Is the same as
flatMap.call([1,2,3,4], x => [x, x])

Sekarang mudah-mudahan jelas bahwa tipe flatMap (bukan _flatMap ) adalah:

function flatMap<T, R>(this: T[], iteratee: (item: T) => R[]): R[]
function flatMap<T, R>(this: undefined, array: T[], iteratee: (item: T) => R[]): R[]

Namun saya tidak punya cara untuk menambahkan types ke portabel karena saya tidak dapat mengekstrak tipe parameter dari _flatMap untuk kemudian digunakan dalam definisi tipe fungsi yang didekorasi, saya membayangkan dengan proposal ini saya bisa menulis sesuatu seperti:

// First argument to func is required for portable to even make sense
function portable<T, R, ...Params>(func: (first: T, ...rest: Params) => R) {
    // The arguments of calling with this is undefined should be simply
    // exactly the same as the input function
    function result(this: undefined, first: T, ...rest: Params): R
    // However when this is of the type of the first argument then the type
    // should be that the parameters are simply the type of the remaining
    // arguments
    function result(this: T, ...rest: Params): R
    function result(...args) {
        if (this === undefined) {
            return func(...args)
        } else {
            return func(this, ...args)
        }
    }
    return result
}

Saya hanya ingin membagikan ini karena ini menunjukkan pengalaman awal saya dengan TypeScript dan mungkin menunjukkan kasus lain mengapa generik variadik penting.

@sandersn :

ini memiliki solusi yang layak - n kelebihan beban

Meskipun secara teknis tidak salah, saya merasa ini tidak sepenuhnya mencerminkan kenyataan di sini. Ya, secara teknis kurangnya ini tidak dapat mencegah kelebihan mengetik fungsi apa pun yang disebutkan di utas ini; namun, ketidaknyamanan kecil ini berarti tidak ada solusi berbasis kelebihan yang berhasil mencapai lib.d.ts sejauh ini.

Dan faktanya, banyak orang di utas ini merasa cukup putus asa dalam menangani fungsi masing-masing untuk mengusulkan lebih banyak sintaks yang awalnya bukan bagian dari proposal ini, termasuk ...... , serta ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> , dan <PromiseLike<T*>> .

Saya pikir ini menunjukkan bahwa kita semua mencoba untuk mengatasi masalah di sini, kita berbagi pengertian umum bahwa kita membutuhkan sintaks yang lebih kuat seperti ini, dan berharap jalan apa pun yang kita pilih di sini akan membantu kita menyelesaikannya.

Catatan: untuk Ramda ini R.path kami telah menghasilkan mengetik baris ribu-ish dari overloads, yang masih merindukan (cara permutasi akan pernah meledak sulit lagi) dukungan tupel, dan hanya menyebabkan kompilasi pada proyek-proyek nyata untuk tidak mengakhiri lagi. Baru-baru ini menemukan iterasi sebagai alternatif yang tampaknya layak di sana (#12290).

Omong-omong, afaik Anda belum mengomentari proposal yang diajukan oleh @Artazor dan @Igorbek. Apa pendapat Anda tentang itu?

Saya ingin berdebat dengan implementasi dasar seperti itu di sini (ditambah #6606) kita bisa melakukan apa saja. Saya akan menawarkan beberapa solusi di sini untuk mengilustrasikan ini, tetapi saya terbuka untuk pertanyaan lebih lanjut.

Biarkan saya membahas beberapa tempat di mana operator ... dapat diimplementasikan:

v ... untuk | definisi (tangkap) | menggunakan (menyebar)
-|-|-
fungsi | type Fn = (...args: any[]) => {} | type Returns = typeof fn(...MyTuple); (#6606)
susunan | perusakan tupel tingkat tipe. secara teknis dapat ditiru menggunakan akses indeks + spread (lihat kanan) + rekursi. | type Arr = [Head, ...Tail];
objek | perusakan objek tingkat tipe. tidak perlu, cukup gunakan Omit , lihat #12215. | type Obj = { a: a, ...restObj }; (tidak perlu, sama seperti Overwrite , lihat #12215)
obat generik | definisikan type Foo<...T> untuk melakukan Foo<1, 2, 3> (menangkap [1, 2, 3 ] ke dalam T ). menyenangkan, tetapi tidak tahu kasus penggunaan apa yang membutuhkan ini. | tentukan type Bar<A,B,C> untuk melakukan Bar<...[1,2,3]> ( A = 1 dll.). ditto, tidak tahu kasus penggunaan yang membutuhkan ini.
serikat pekerja (bonus) | ? | type Union = "a" | "b"; type MyTuple = ...Union; // ["a", "b"] (pesanan tidak dapat diandalkan tetapi memungkinkan iterasi serikat/objek melalui tupel. Lagi pula, jauh dari ruang lingkup di sini.)

Jadi hanya ada dua tingkat tipe ... instans yang langsung relevan; secara khusus, keduanya digunakan di sini:

declare function f<U, T>(head: U, ...tail: T): [U, ...T];

Dalam konteks #6606, hal lain menjadi relevan: kemampuan untuk membongkar tipe Tuple untuk aplikasi fungsi, misalnya typeof f(...MyTuple) . Saya pikir ini cukup untuk memecahkan masalah yang lebih sulit yang pernah saya dengar disebutkan di sini. Untuk mencoba menawarkan beberapa solusi di sini:

@jameskeane :

Saya pikir harus ada cara untuk mewakili 'tipe elemen' dari sebuah Tuple

Jika Anda ingin mendapatkan penyatuan elemen-elemennya, lihat TupleToUnion .

Promise.all

// helpers: `mapTuple` needs #5453 to define, #6606 to use
type TupleHasIndex<Arr extends any[], I extends number> = ({[K in keyof Arr]: '1' } & Array<'0'>)[I];
type Inc = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // longer version in gist
declare function mapTuple<F extends (v: T) => any, Tpl extends T[], T>(f: F, tpl: Tpl): MapFn<F, Tpl, T>;
type MapFn<
    F extends (v: T) => any,
    Tpl extends T[],
    T,
    // if empty tuple allowed:
    // I extends number = 0,
    // Acc = []
    // otherwise:
    I extends number = 1,
    Acc = [F(Tpl[0])]
> = { 1: MapFn<F, Tpl, T, Inc[I], [...Acc, F(Tpl[I])]>; 0: Acc; }[TupleHasIndex<Tpl, Int>];

declare module Promise {
  function all<Promises extends Promise<any>[]>(promises: Promises): typeof mapTuple(<T>(prom: Promise<T>) => T, Promises);
}

@danvk :

_.extend

@sandersn :

Object.assign

Keduanya hanyalah versi variadic dari Ramda's mergeAll . Tidak perlu enam titik!

@isiahmeadows :

Adakah ide tentang apa status masalah ini?
Sintaks dan semantik tingkat rendah belum memiliki konsensus yang jelas.

Jika saya mengerti dengan benar, Anda terutama khawatir apakah pendekatan yang ditawarkan oleh beberapa yang lain akan mempertimbangkan untuk menangani pengetikan yang lebih sulit seperti curry dan bind yang telah Anda sebutkan. Inilah pendapat saya tentang yang satu itu, mengikuti proposal mereka.
Strateginya agak mirip, menipu fakta yang sulit untuk dikatakan, mengekstrak persyaratan tipe untuk params i~j dari tipe fungsi menjadi tipe Tuple, dengan menunda pemeriksaan tipe argumen ke aplikasi fungsi.

// helpers in https://gist.github.com/tycho01/be27a32573339ead953a07010ed3b824, too many to include

// poor man's version, using a given return value rather than using `typeof` based on the given argument types:
function curry<Args extends any[], Ret>(fn: (...args: Args) => Ret): Curried<Args, Ret>;
type Curried<
  ArgsAsked,
  Ret,
  ArgsPrevious = [] // if we can't have empty tuple I guess any[] might also destructures to nothing; that might do.
> = <
  ArgsGiven extends any[] = ArgsGiven,
  ArgsAll extends [...ArgsPrevious, ...ArgsGiven]
      = [...ArgsPrevious, ...ArgsGiven]
  >(...args: ArgsGiven) =>
    If<
      TupleHasIndex<ArgsAll, TupleLastIndex<ArgsAsked>>,
      Ret,
      Curried<ArgsAsked, Ret, ArgsAll>
    >;

// robust alternative that takes into account return values dependent on input params, also needs #6606
function curry<F>(fn: F): Curried<F>;
type Curried<
  F extends (...args: ArgsAsked) => any,
  ArgsAsked extends any[] = ArgsAsked,
  ArgsPrevious = []
> = <
  ArgsGiven extends any[] = ArgsGiven,
  ArgsAll extends [...ArgsPrevious, ...ArgsGiven]
      = [...ArgsPrevious, ...ArgsGiven]
  >(...args: ArgsGiven) =>
    If<
      TupleHasIndex<ArgsAll, TupleLastIndex<ArgsAsked>>,
      F(...[...ArgsPrevious, ...ArgsGiven]), // #6606
      Curried<ArgsAsked, Ret, ArgsAll>
    >;

// bind:
interface Function {
    bind<
        F extends (this: T, ...args: ArgsAsked) => R,
        ArgsAsked extends any[],
        R extends any,
        T,
        Args extends any[], // tie to ArgsAsked
        Left extends any[] = DifferenceTuples<ArgsAsked, Args>,
        EnsureArgsMatchAsked extends 0 = ((v: Args) => 0)(TupleFrom<ArgsAsked, TupleLength<Args>>)
        // ^ workaround to ensure we can tie `Args` to both the actual input params as well as to the desired params. it'd throw if the condition is not met.
    >(
        this: F,
        thisObject: T,
        ...args: Args
    ): (this: any, ...rest: Left) => R;
    // ^ `R` alt. to calc return type based on input (needs #6606): `F(this: T, ...[...Args, ...Left])`
}

Ya, saya menggunakan banyak tipe pembantu -- hanya mencoba puas dengan apa yang kami miliki (+ bayangkan apa yang bisa kami lakukan dengan sedikit lebih banyak). Saya tidak begitu menentang ...... , ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> , atau <PromiseLike<T*>> . Tetapi IMO, bahkan hanya ... membantu mengatasi masalah nyata saat ini, dan saya ingin melihatnya ditangani.

Sunting: Saya memecahkan batasan argumen untuk bind .

Hanya pertanyaan yang mungkin bodoh. Ini terlihat sangat menjanjikan untuk dapat mengetik fungsi kari dengan benar.
Tetapi untuk beberapa proyek dunia nyata, seseorang tidak ingin menghabiskan banyak waktu untuk mengetikkan potongan kode berorientasi pemrograman yang sangat fungsional.
Jadi, saya bertanya-tanya, karena --strict diaktifkan secara default di _tsconfig.json_, apakah mungkin ada cara untuk menonaktifkan pemeriksaan tipe untuk sebagian kode (karena kemalasan atau kurangnya waktu).
Tapi, seperti yang saya katakan, itu mungkin pertanyaan bodoh... ^_^

@yahiko00 agak di luar topik, tetapi gunakan bagian exclude di tsconfig atau tsconfig yang berbeda pada tingkat proyek yang berbeda.

Saya juga ingin membuat saran lain, bisakah kita memilikinya sehingga & dan | bekerja dengan satu argumen Tuple dengan sintaks ini:

<...T>(...args:T): ...T&
// is the same as 
<t1, t2, t3>(...args:[t1, t2, t3]): t1 & t2 & t3;
// and
<....T>(...args:T): ...T|
// is the same as 
<t1, t2, t3>(...args:[t1, t2, t3]): t1 | t2 | t3;

Saran @HyphnKnight di atas akan sangat berguna untuk sesuatu yang saya lakukan juga.

Saya ingin menambahkan penafian bahwa proposal ini tidak sedang dikerjakan secara aktif. Tetapi saya menemukan jenis kertas "seni sebelumnya" yang ingin saya baca ketika saya pertama kali mulai melihat masalah ini: http://www.ccs.neu.edu/racket/pubs/esop09-sthf.pdf

Saya akan meninggalkannya di sini untuk referensi di masa mendatang.

Saya membuka beberapa PR bereksperimen ke arah ini:

  • [ ] #17884 spread dalam tipe tuple (WIP)
  • [x] #17898 ekstrak sisa params (siap)
  • [ ] #18007 spread dalam jenis panggilan (WIP)
const c = 'a' + 'b';

Dapat memecahkan masalah? Jenis kesimpulan dari c adalah 'ab' bukan string

Pertanyaan terkait di StackOverflow: Parameter fungsi terakhir eksplisit di TypeScript

@sandersn Proposal Anda akan mencakup kasus ini, sejauh yang saya bisa lihat, apakah ini benar?

Lebih dari 2 tahun sejak proposal awal, haruskah kita tetap berharap?

Halo!
Saya mencoba mengetik generator yang mengambil sejumlah variabel array dan mencampur dan mencocokkan elemen-elemennya untuk membuat array baru.
Saya ingin menggunakan generator ini dalam loop for...of , tetapi tidak bisa mengetik nilai dengan benar.
Kode (mungkin ada kesalahan karena saya belum menjalankannya, tetapi inilah yang saya coba lakukan):

function* CombineEveryArgumentWithEveryArgument(...args: any[][]) {
    if (args.length < 1) {
        return [];
    }
    var haselements = false;
    for (var arg of args) {
        if (arg && arg.length > 0) {
            haselements;
        }
    }
    if (!haselements) {
        return [];
    }
    var indexes = [];
    for (var i = 0; i < args.length; i++) {
        indexes.push(0);
    }
    while (true) {
        var values = [];
        //One item from every argument.
        for (var i = 0; i < args.length; i++) {
            values.push(args[i][indexes[i]]);
        }
        if (indexes[0] + 1 < args[0].length) {
            yield values;
        }
        else {
            return values;
        }
        //Increment starting from the last, until we get to the first.
        for (var i = args.length; i > 0; --i) {
            if (indexes[i]++ >= args[i].length) {
                indexes[i] = 0;
            }
            else {
                break;
            }
        }
    }
}

Contoh penggunaan:

for (let [target, child] of
    CombineEveryArgumentWithEveryArgument(targetsarray, childrenarray)) {

Saya tidak dapat menemukan cara untuk mengetik target dan anak tanpa membuat variabel perantara.

Sesuatu seperti ini akan baik?

function * generator<...T[]>(...args: T[]): [...T]

@Griffork praktik yang benar, hingga proposal ini diimplementasikan, adalah membuat banyak kelebihan untuk fungsi
misalnya, lihat janji. semua jenis
https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L41 -L113

Saya menemukan sintaks ini sangat membingungkan:

function apply<...T,U>(ap: (...args:...T) => U, args: ...T): U {

ini terasa jauh lebih alami bagi saya:

function apply<T, U>(ap: (...args: T) => U, args: T): U {

Saat runtime, parameter rest adalah array, dan kita dapat melakukannya saat ini di TS:

function apply<T, U>(ap: (...args: T[]) => U, args: T[]): U {

Jadi tampaknya logis untuk menghapus batasan args menjadi array T dan alih-alih mengaktifkan kompiler TS untuk menyimpulkan tipe Tuple untuk T , misalnya

function apply(ap: (...args: [number, number]) => number, args: [number, number]): number {

Saya melihat bahwa ada beberapa kekhawatiran yang muncul tentang tupel dan saya tidak sepenuhnya memahami semuanya, tetapi saya hanya ingin mempertimbangkan bahwa dalam proposal saat ini sulit untuk memahami kapan pengembang perlu menggunakan ... dalam posisi tipe dan tupel jauh lebih intuitif.

... masih masuk akal bagi saya untuk menggabungkan dua tipe Tuple, seperti [...T, ...U] .

@felixfbecker
Usulan untuk

function apply<...T,U>(ap: (...args:T) => U, ...args: T): U {

Apakah T adalah tipe tuple yang dibuat secara dinamis, jadi jika Anda meneruskan string dan int ke dalam fungsi, maka T adalah [string, int] .
Ini sangat menarik jika Anda ingin mengekspresikan pola secara dinamis seperti ini:

function PickArguments<T>(a: T[]): [T];
function PickArguments<T, U>(a: T[], b: U[]): [T, U];
function PickArguments<T, U, V>(a: T[], b: U[], c: V[]): [T, U, V];
//More overloads for increasing numbers of parameters.

//usage:
var [a, b, c] = PickArguments(["first", "second", "third"], [1, 2, 3], [new Date()]);
var d = b + 1; //b and d are numbers.
var e = c.toDateString(); //c is a date (autocompletes and everything), e is a string.

Saat ini jika Anda ingin menulis fungsi yang mengambil sejumlah variabel argumen dan mengetiknya secara umum, Anda harus menulis kelebihan beban generik untuk setiap jumlah argumen yang mungkin diberikan oleh fungsi tersebut. Proposal ...T pada dasarnya memungkinkan kita untuk membuat kompiler secara otomatis menghasilkan definisi fungsi untuk kita.

Usulan Anda:

function apply<T, U>(ap: (...args: T) => U, args: T): U {

Memaksa semua parameter untuk diperlakukan sebagai tipe yang sama, dan mereka tidak dapat memiliki pemeriksaan tipe yang lebih spesifik. Misalnya dalam contoh saya di atas, semua nilai yang dikembalikan akan bertipe any .

Saya juga menemukan tambahan ... sangat sulit untuk dibaca.
Seperti ide @felixfbecker , saya tidak melihat perlunya melakukan:

function apply<...T, U>(ap: (...args: ...T) => U, args: ...T): U {...}

Hal pertama yang terlintas dalam pikiran ketika membaca apply<...T, adalah bahwa ini adalah operator spread, tetapi sebenarnya tidak melakukan spread sama sekali.

@Griffork , dalam contoh Anda T akan tetap [string, int] .
Itulah yang dimaksud @felixfbecker dengan "alih-alih mengaktifkan kompiler TS untuk menyimpulkan tipe Tuple untuk T", setidaknya seperti yang saya pahami.

Memaksa semua parameter untuk diperlakukan sebagai tipe yang sama, dan mereka tidak dapat memiliki pemeriksaan tipe yang lebih spesifik. Misalnya dalam contoh saya di atas, semua nilai yang dikembalikan akan bertipe apa saja.

@Griffork Tidak, dalam pikiran saya itu akan menyimpulkan tipe Tuple untuk array args , memberikan setiap parameter tipenya sendiri berdasarkan posisinya di Tuple. ...args: T[] akan memaksa mereka semua untuk menjadi tipe yang sama T , tetapi ...args: T (yang saat ini merupakan kesalahan kompilasi) akan menyimpulkan tipe Tuple untuk T .

Hal pertama yang terlintas dalam pikiran ketika membaca apply<...T, adalah bahwa ini adalah operator spread, tetapi sebenarnya tidak melakukan spread sama sekali.

@unional setuju, dari situlah kebingungan berasal.

@unional
Saya membacanya sebagai operator spread juga, saya membacanya sebagai "sebarkan jenis ini setiap kali digunakan".
Bagi saya, membaca ini

function apply<T, U>(ap: (...args: T) => U, args: T): U {

Saya berharap T menjadi array sesuatu (misalnya string[] ).

Dan membaca ini:

function apply<T, U>(ap: (...args: T[]) => U, args: T[]): U {

Saya berharap semua argumen dapat ditetapkan ke tipe T (yang merupakan satu tipe, seperti string ).

Maksud dari proposal di atas adalah untuk menghindari pembuatan generik yang secara implisit mampu mewakili jumlah tipe yang berubah-ubah.

@felixfbecker
Sunting:
Oh oke. Masih tidak berpikir itu intuitif.

Saya berharap T menjadi array sesuatu (mis. string[]).

Tuple adalah "sesuatu array", itu hanya sebuah array dengan panjang tetap dan tipe spesifik untuk setiap elemen, misalnya [string, number] (vs (string | number)[] , yang tidak terikat dan tidak menyatakan elemen apa yang memiliki apa Tipe).

Nah lalu apa yang Anda ketik jika Anda benar-benar menginginkan perilaku itu?

Tidak yakin perilaku apa yang sebenarnya Anda maksud, tetapi saya berasumsi "memaksa semua parameter menjadi dari jenis yang sama", yang akan diselesaikan oleh ...args: T[] .

Saya membacanya sebagai operator spread juga, saya membacanya sebagai "sebarkan jenis ini setiap kali digunakan".

Itu sebabnya saya pikir itu membingungkan.
Ketika Anda menyebar, Anda hanya melakukannya, Anda tidak akan mendeklarasikan sesuatu yang "dapat disebarkan":

const a = { x: 1, y: 2 }
const b = { ...a }

// likewise
function appendString<T>(...args: T): [...T, string] {
  args.push('abc')
  return args
}

Ya. Jika Anda ingin mendeklarasikan bahwa argumen tipe generik harus "dapat disebarkan" (yang menurut spesifikasi ES hanya berarti harus dapat diubah), kami sudah memiliki cara untuk mengekspresikannya dalam TypeScript dengan extends :

function foo<T extends Iterable<any>>(spreadable: T): [...T, string] {
  return [...spreadable, 'abc']
}

const bar = foo([1, true])
// bar is [number, boolean, string]

tentu saja, dalam kasus parameter istirahat diketahui bahwa itu bukan hanya Iterable, tetapi sebuah Array.

Yang, apa yang kami katakan telah diusulkan: https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189703556

Tapi sisanya terlalu lama untuk dikonsumsi dalam satu tempat duduk. 🌷

Jika rangkaian tupel mendarat, maka kita dapat menerapkan nomor gereja! Hore!

type TupleSuc<T extends [...number]> = [...T, T['length']];
type TupleZero = [];  // as proposed, we need empty tuple
type TupleOne = TupleSuc<TupleZero>;
type Zero = TupleZero['length'];
type One = TupleOne['length'];

Dan jika rekursi tipe kondisional berfungsi, kita dapat membuat Tuple dengan panjang yang diinginkan:

type Tuple<N extends number, T = TupleZero> = T['length'] extends N ? T : Tuple<N, TupleSuc<T>>;
type TupleTen = Tuple<10>;
type Ten = TupleTen['length'];

Saya tidak menganggap saya telah membaca semua utas ini, tetapi jika memiliki ...T dalam parameter umum membingungkan,
mengapa tidak mencoba untuk mencerminkan sintaks perusakan tingkat nilai lebih lanjut, sehingga menggunakan [...T] dalam a
posisi argumen level tipe merusak tipe jika itu adalah Tuple level tipe? Ini juga akan membutuhkan
memungkinkan mengetik params istirahat dengan tupel, yang akan membuat yang berikut ini setara di situs panggilan
di TypeScript:

const first = (a: number, b: string) => …;
const second = (...ab: [number, string]) => …;

first(12, "hello"); // ok
second(12, "hello"); // also ok

INB4 "tapi itu adalah pancaran yang diarahkan ke tipe" – tidak. Ini tidak mengubah emisi apa pun, first masih memiliki dua
argumen berbeda yang dikeluarkan, second masih akan memiliki satu argumen istirahat. Satu-satunya hal yang berubah adalah
bahwa di situs panggilan, TypeScript akan memeriksa apakah parameter second cocok, secara berurutan, Tuple
[number, string] .

Bagaimanapun, dengan asumsi kita menerima sintaks [...Type] , maka kita dapat menulis apply sebagai berikut:

function apply<
  [...ArgumentsT], // a type-level tuple of arguments
  ResultT
>(
  // the call site of `toApply` function will be used to infer values of `ArgumentsT`
  toApply:   (...arguments: ArgumentsT) => ResultT,
  arguments: ArgumentsT
) :
  ResultT
{
  // …
}

// NB: using my preferred formatting for complex type-level stuff; hope it's readable for you
// this is entirely equivalent to OP's notation version:
function apply<[...T], U>(ap: (...args: T) => U,  args: T): U {
  // …
}

// so at the call site of
const fn = (a: number, b: string, c: RegExp) => …;

// we have `ArgumentsT` equal to [number, string, RegExp]
apply(fn, [12, "hello" /s+/]); // ok, matches `ArgumentsT`
apply(fn, [12, /s+/]); // not ok, doesn't match `ArgumentsT`

Sintaks [...Type] akan berperilaku sepenuhnya sebagai perusakan tingkat nilai, memungkinkan pemisahan
dan bergabung dengan tupel tingkat tipe, sesuai kebutuhan:

type SomeType  = [string, number, "constant"];
type OtherType = ["another-constant", number];

type First<[First, ..._]> = FirstT;
type Rest<[_, ...RestT]> = RestT;
type Concat<[...LeftT], [...RightT]> = [...LeftT, ...RightT];
type FirstTwo<[FirstT, SecondT, ..._]> = [FirstT, SecondT];

// has type `string`
const aString: First<SomeType> =
  "strrriiing";
// has type `[number, "constant"]
const numberAndConstant: Rest<SomeType> =
  [42, "constant"];
// has type `[string, number, "constant", "another-constant", number]`
const everything: Concat<SomeType, OtherType> =
  ["herpderp", 42, "constant", "another-constant", 1337];
// has type `[string, number]`
const firstTwo: FirstTwo<SomeType> =
  ["striiiing", 42];

Contoh cara mengetikkan fungsi curry menggunakan ini:

type Curried<
  [...ArgumentsT]
  ResultT,
  ArgumentT      = First<ArgumentsT>,
  RestArgumentsT = Rest<ArgumentsT>
> =
  // just ye olde recursione, to build nested functions until we run out of arguments
  RestArgumentsT extends []
    ? (argument: ArgumentT) => ResultT
    : (argument: ArgumentT) => Curried<RestArgumentsT, ResultT>;

// NB. with more complex generic types I usually use generic defaults as a sort-of
// of type-level variable assignment; not at all required for this, just nicer to read IMO

function curry<
  [...ArgumentsT],
  ResultT
>(
  function: (...arguments: ArgumentsT) => ResultT
) :
  Curried<ArgumentsT, ResultT>
{
  // do the magic curry thing here
}

// or in the short indecipherable variable name style

function curry<[...T], U>(fn: (...args: T) => U): Curried<T, U>
{
  // …
}

// this should let you do this (using `fn` from before)
const justAddRegex = curry(fn)(123, "hello");

justAddRegex(/s+/); // ok, matches the arguments of `fn`
justAddRegex(123); // not ok, doesn't match the arguments of `fn`

Saya berasumsi akan sangat membantu untuk dapat mengatakan, bahwa beberapa argumen tipe adalah Tuple tingkat tipe
dari beberapa jenis. Masalahnya adalah bagaimana – mengingat sejak 2.7 (saya pikir?) tuple assignability membutuhkan
memperhitungkan panjang tupel – untuk mengekspresikan konsep _setiap tipe-tingkat tuple_. Tapi mungkin sesuatu seperti
[...] bisa bekerja? Saya tidak memiliki pendapat yang kuat, tetapi akan lebih baik jika konsepnya dapat dinamai.

// bikeshed me
type OnlyTuplesWelcome<ArgumentT extends [...]> = ArgumentT;

Dalam hal ini sintaks [...ArgsT] pada dasarnya bisa menjadi singkatan untuk ArgsT extends [...] ,
memiliki penggunaan perusakan tingkat tipe menyiratkan batasan pada tipe untuk menjadi tupel tingkat tipe.

Pikiran?

@jaen :

(...ab: [number, string]) => …

Ya, itu terlihat seperti #4130. Saya mencoba sesuatu di #18004, tetapi pendekatan saya agak kacau (simpul sintetis).

Saat mengekspresikan Tuple apa pun, saya pernah melihat seseorang menggunakan any[] & { 0: any } , yang menurut saya berfungsi hingga tipe Tuple kosong fwiw. Saya tidak terlalu repot secara pribadi, kebanyakan hanya menerima any[] .

RxJS membutuhkan ini di semua tempat. Paling kritis untuk Observable.prototype.pipe , yang saat ini kami memiliki banyak kelebihan, tetapi saya selalu diminta untuk menambahkan "hanya satu level lagi".

Untuk @benlesh kedua kami menggunakan RXJS secara ekstensif dan membutuhkan ini untuk fungsi pipa.

Saya penulis ppipe , yang, seperti fungsi pipa di RXJS, membutuhkan ini. Saya pikir saya melihat pola di sini ^^

Saya penulis runtypes , dan fitur ini sangat dibutuhkan untuk mengekspresikan serikat pekerja dan persimpangan. Satu-satunya solusi (tidak lengkap) adalah kelebihan beban yang sangat besar:

https://github.com/pelotom/runtypes/blob/master/src/types/union.ts

🤢

Saya telah menulis ulang jenis ramda , yang juga membutuhkan ini misalnya pipe , lensa, dan kari.
Kami membutuhkan codegen karena kari yang dibuat secara langsung mempertahankan kelebihan beban tidak dapat dikelola. Jenis path membentang lebih dari seribu baris, di mana kami menemukan kinerja jenis kelebihan beban juga menjadi masalah.

Apakah masalah dengan menyimpulkan dan menerapkan argumen istirahat telah diselesaikan?

function example(head: string, ...tail: number[]): number[] {
  return [Number(head), ...tail]
}

function apply<T, U>(fn: (...args: T) => U, args: T): U {
  return fn.apply(null, args)
}

Jika tipe T dalam apply(example, ['0', 1, 2, 3]) disimpulkan sebagai [string, number[]] , panggilan untuk menerapkan akan menimbulkan kesalahan.

Itu berarti tipe T benar-benar

type T = [string, ...number[]]

atau

type T =
  {0: string} &
  {[key: Exclude<number, 0>]: number} &
  Methods

Benar-benar binatang yang aneh, tapi mengingat bagaimana ({0: string} & Array<number>)[0] akan
saat ini memutuskan untuk string [1] tampaknya mungkin untuk menyandikan tanpa banyak perubahan
ke sistem tipe.

[1] Apakah ini bug, haruskah itu string | number ?

Maaf mengganggu 36 peserta tentang masalah ini (tip: gunakan ini ), tetapi bagaimana kami dapat memantau jika ini masih dipertimbangkan, jika ini ada di peta jalan, dll?

Sangat menyedihkan bahwa belum ada yang ditugaskan untuk itu setelah 2 setengah tahun, tampaknya menjadi fitur yang cukup penting :(

PS: Saya sudah membaca beberapa lusin komentar, mencoba Cmd+F dll, tidak menemukan info ini.

@brunolemos ada referensi untuk tipe variadic dalam pertemuan desain terbaru
https://github.com/Microsoft/TypeScript/issues/23045
Untuk membuat fitur ini, mereka harus terlebih dahulu membuat lebih primitif dan konsep secara iteratif, dan ketika ada cukup fondasi, saya yakin mereka akan menambahkannya ke tonggak sejarah apa pun

saya tidak bisa melakukan ini

type Last<T extends any[]> =
    T extends [infer P] ? P :
    ((...x: T) => any) extends ((x: any, ...xs: infer XS) => any) ? Last<XS> :

Ini soal #14174 tapi sebagai relasi

@kgtkr untuk referensi lihat trik @fightingcat agar kompiler mengabaikan rekursi.

Terima kasih

type Last<T extends any[]> = {
    0: never,
    1: Head<T>,
    2: Last<Tail<T>>,
}[T extends [] ? 0 : T extends [any] ? 1 : 2];

Hm, saya punya pertanyaan. Saya punya kode seperti ini untuk menangani mixin:

export const Mixed = <

    OP = {}, OS = {}, // base props and state
    AP = {}, AS = {}, // mixin A props and state
    BP = {}, BS = {}, // mixin B props and state
    // ...and other autogenerated stuff
>(

    // TODO: Find a way to write that as ...args with generics:
    a?: ComponentClass<AP, AS>,
    b?: ComponentClass<BP, BS>,
    // ...and other autogenerated stuff

) => {

    type P = OP & AP & BP;
    type S = OS & AS & BS;
    const mixins = [a, b];

    return class extends Component<P, S> {
        constructor(props: P) {
            super(props);
            mixins.map(mix => {
                if (mix) {
                    mix.prototype.constructor.call(this);
                    // some state magic...
                }
            });
        }
    };
};

Saya menggunakannya seperti berikut:

class SomeComponent extends Mixed(MixinRedux, MixinRouter, MixinForm) {
     // do some stuff with mixed state
}

Ini berfungsi seperti yang diharapkan - dengan pengetikan yang tepat, penanganan status, dan sebagainya, tetapi apakah ada cara untuk menulis ulang dengan cara yang lebih singkat tanpa menunggu jenis variadik? Karena saya merasa sedikit bodoh tentang yang satu ini sekarang.

Dengan 3.0 sekarang dimungkinkan untuk mendeklarasikan rest args sebagai Tupple

declare function foo(...args: [number, string, boolean]): void;

Tetapi apakah mungkin untuk mendapatkan tipe argumen Tuple dari fungsi yang diberikan secara terbalik?

Sesuatu seperti Arguments<foo> akan menyenangkan.

@whitecolor bagaimana dengan ini?

type Arguments<F extends (...x: any[]) => any> =
  F extends (...x: infer A) => any ? A : never;

dengan TS 3.0 kita bisa melakukan ini sekarang

function compose<X extends any[], Y extends any[], Z extends any[]>(
  f: (...args: X) => Y,
  g: (...args: Y) => Z
): (...args: X) => Z {
  return function (...args) {
    const y = f(...args);
    return g(...y);
  };
}

tetapi kami memiliki masalah kecil, kami harus mendeklarasikan fungsi yang mengembalikan acara tupples untuk params tunggal dan juga menangani void entah bagaimana dan kami harus mendeklarasikan tipe pengembalian jika tidak maka disimpulkan sebagai array :)

https://www.typescriptlang.org/play/index.html#src =%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0Afunction%20foo0() %3A%20void%20%7B%0D%0A%20%0D%0A%7D%0D%0A%0D%0Afunction%20bar0()%3A%20void%20%7B%0D%0A%0D%0A%7D %0D%0A%0D%0Afunction%20foo1(a%3A%20string)%3A%20%5Bstring%5D%20%7B%0D%0A%20%20return%20%5Ba%5D%3B%0D%0A% 7D%0D%0A%0D%0Afunction%20bar1(a%3A%20string)%3A%20%5Bstring%5D%20%7B%0D%0A%20%20return%20%5Ba%5D%3B%0D%0A %7D%0D%0A%0D%0Afunction%20foo2(a1%3A%20string%2C%20a2%3A%20boolean)%3A%20%5Bstring%2C%20boolean%5D%20%7B%0D%0A%20% 20return%20%5Ba1%2C%20a2%5D%3B%0D%0A%7D%0D%0A%0D%0Afunction%20foo21(a1%3A%20string%2C%20a2%3A%20boolean)%3A%20%5Bstring %5D%20%7B%0D%0A%20%20return%20%5Ba1%5D%3B%0D%0A%7D%0D%0A%0D%0A%0D%0Afunction%20bar2(a1%3A%20string%2C %20a2%3A%20boolean)%3A%20%5Bstring%2C%20boolean%5D%20%7B%0D%0A%20%20return%20%5Ba1%2C%20a2%5D%3B%0D%0A%7D% 0D%0A%0D%0A%0D%0Afunction%20compose%3CX%20extends%20any%5B%5D%2C%20Y%20extends%20any%5B%5D%2C%20Z%20extends%20any%5B%5D%3E( %0D%0A%20%20f%3A%20(... args%3A%20X)%20%3D%3E%20Y%2C%0D%0A%20%20g%3A%20(...args%3A%20Y)%20%3D%3E%20Z%0D%0A )%3A%20(...args%3A%20X)%20%3D%3E%20Z%20%7B%0D%0A%20%20return%20function%20(...args)%20%7B% 0D%0A%20%20%20%20const%20y%20%3D%20f(...args)%3B%0D%0A%20%20%20%20return%20g(...y)%3B% 0D%0A%20%20%7D%3B%0D%0A%7D%0D%0A%0D%0A%0D%0A%0D%0A%0D%0Aconst%20baz0%20%3D%20compose(%0D%0A %20%20foo0%2C%0D%0A%20%20bar0%0D%0A)%3B%0D%0A%0D%0Aconst%20baz21%20%3D%20compose(%0D%0A%20%20foo21%2C%0D %0A%20%20bar1%0D%0A)%3B%0D%0Aconst%20baz2%20%3D%20compose(%0D%0A%20%20foo2%2C%0D%0A%20%20bar2%0D%0A)% 3B%0D%0A%0D%0A%0D%0Aalert(baz2('a'%2C%20false))%0D%0Aalert(baz21('a'%2C%20true))%0D%0Alert(baz0())

@maciejw
Gunakan tipe bersyarat:

function compose<X extends any[], Y extends any, Z extends any>(
  f: (...args: X) => Y,
  g: Y extends any[] ? (...args: Y) => Z : () => Z
): (...args: X) => Z {
    return function (...args) {
        const y = (f as any)(...args);
        return (g as any)(...y);
    } as any;
}

tentu, tapi ini agak retas dengan as any :) Saya pikir akan keren jika sistem tipe mendukung ini tanpa peretasan

Jenis-jenis dalam badan fungsi dapat berupa banyak hal, tidak banyak yang dapat dilakukan tentang hal itu — Anda dapat melakukan sesuatu seperti (f as (...args: any[]) => Y) sebagai gantinya tetapi saya pikir itu mengurangi kejelasan tanpa alasan yang jelas

Jika tipe variadik membuahkan hasil, saya akan dapat menggeneralisasi beberapa kode TypeScript yang saya tulis untuk proyek saya sendiri, yang memungkinkan saya sepenuhnya menentukan bentuk REST API saya, dan menerapkan bentuk itu pada jenis server Node yang sesuai dan JavaScript perpustakaan klien untuk itu.

Generalisasi yang memungkinkan saya menyederhanakan kode saya sendiri, serta melakukan hal yang sama untuk API arbitrer, dan melangkah lebih jauh dan mungkin menghasilkan definisi Swagger untuk klien bahasa lain juga... dapat berguna bagi orang lain! Hanya bermimpi dengan keras haha

@kgtkr : Kelihatannya bagus! :)

Jenis Pipa merusak taman bermain TS untuk saya (meskipun yang lain berfungsi dengan baik), saya kira itu membutuhkan TS terbaru?

TS juga menunjukkan beberapa kesalahan kedalaman rekursi -- sepertinya @isiahmeadows membuka #26980 untuk itu.

@tycho01

Jenis Pipa merusak taman bermain TS untuk saya (meskipun yang lain berfungsi dengan baik), saya kira itu membutuhkan TS terbaru?

Itu hang untuk saya juga, dan saya harus meretas ke devtools untuk memaksanya melakukan kesalahan untuk keluar darinya.

TS juga menunjukkan beberapa kesalahan kedalaman rekursi -- sepertinya @isiahmeadows membuka #26980 untuk itu.

Itu untuk sesuatu yang terkait, tetapi berbeda: mengangkat batasan dengan tipe bersyarat karena dua alasan:

  • Untuk mempermudah waktu melakukan pekerjaan yang lebih kompleks seperti iterasi daftar. Itu juga akan meletakkan kerangka kerja untuk membuka hal-hal seperti matematika integer tingkat tipe tanpa menabrak kompiler atau berakhir dengan kekacauan ad-hoc.
  • Untuk mengatasi masalah apa yang membuat sistem tipe Turing-lengkap dengan lebih tepat, sehingga dapat berpotensi diperbaiki dengan alat yang memanfaatkannya atau dihapus dengan memberlakukan penghentian yang dapat dibuktikan di jalan.

Jika menghapus tipe yang diindeks tidak cukup untuk membuat sistem tipe tidak Turing-lengkap apa adanya, jangan ragu untuk memberikan komentar di sana sehingga saya dapat memperbaruinya. (Saya tidak mengusulkan untuk menghapusnya, tentu saja. Saya hanya mengusulkan untuk menanganinya dengan lebih baik secara internal untuk memperingatkan orang-orang tentang potensi loop tak terbatas.)

Setelah dipikir-pikir, jenis Pipa ini terasa seperti cara yang sangat berbelit-belit untuk melakukan (vs...: Params<T[0]>) => ReturnType<Last<T>> . Setiap iterasi di luar itu (selain dari pemeriksaan param perantara) mungkin menjadi lebih berguna dengan tipe pengembalian yang bergantung pada input.

@tycho01 Ini mencoba mengetikkan hal-hal seperti ini , di mana tipenya pada dasarnya adalah ini:

   f1,     f2,   ...,   fm,     fn    -> composed
(a -> b, b -> c, ..., x -> y, y -> z) -> (a -> z)

Anda harus mengulangi parameter satu per satu untuk mengetiknya dengan benar, karena parameter parameter berikutnya bergantung pada nilai pengembalian parameter sebelumnya, dan Anda juga harus memperhitungkan parameter pertama dan nilai pengembalian akhir (yang tidak @kgtkr ) . Versi "ditingkatkan" saya di #26980 membuat pengoptimalan untuk menggunakan akumulator sebagai gantinya, jadi saya mengulangi argumen jauh lebih sedikit, tetapi juga membuatnya lebih benar.


Jika Anda melihat milik saya di #26980, itu sedikit lebih jelas apa yang seharusnya dilakukan (lebih sedikit mengejar angka), dan itulah bagian dari alasan saya mengajukan permintaan fitur itu.

@tycho01 @kgtkr BTW, saya memperbarui bug itu dengan cuplikan PipeFunc dikoreksi , disalin di sini untuk kenyamanan:

type Last<L extends any[], D = never> = {
    0: D,
    1: L extends [infer H] ? H : never,
    2: ((...l: L) => any) extends ((h: any, ...t: infer T) => any) ? Last<T> : D,
}[L extends [] ? 0 : L extends [any] ? 1 : 2];

type Append<T extends any[], H> =
    ((h: H, ...t: T) => any) extends ((...l: infer L) => any) ? L : never;

type Reverse<L extends any[], R extends any[] = []> = {
    0: R,
    1: ((...l: L) => any) extends ((h: infer H, ...t: infer T) => any) ?
        Reverse<T, Append<R, H>> :
        never,
}[L extends [any, ...any[]] ? 1 : 0];

type Compose<L extends any[], V, R extends any[] = []> = {
    0: R,
    1: ((...l: L) => any) extends ((a: infer H, ...t: infer T) => any) ?
        Compose<T, H, Append<R, (x: V) => H>>
        : never,
}[L extends [any, ...any[]] ? 1 : 0];

export type PipeFunc<T extends any[], V> =
    (...f: Reverse<Compose<T, V>>) => ((x: V) => Last<T, V>);

Yang ini tidak crash di taman bermain, BTW. Ini benar-benar mengetik-cek dan melakukannya dengan cukup cepat.

Saya belum mengujinya pada tipe _.flow atau _.flowRight potensial, tetapi itu akan berfungsi sebagai titik awal.

@tycho01
yg dibutuhkan
naskah@selanjutnya
3.0.1/3.0.2 tidak berfungsi

Berkat utas ini, saya membuat ini

Teman-teman, tolong berhenti memposting informasi yang hampir tidak relevan dengan diskusi tentang masalah ini. Banyak orang yang mengikuti diskusi ini, karena kami menginginkan jenis yang bervariasi. Saya telah menerima lebih dari 10 email dalam beberapa hari terakhir yang tidak relevan dengan alasan saya mengikuti masalah ini.
Saya berharap ada orang lain yang sependapat dengan saya. Sampai saat ini saya hanya berharap itu akan berhenti, karena saya tidak ingin berkontribusi pada spam. Tapi serius, cukup sudah.
PS Maaf atas pemberitahuan ini, kepada siapa pun yang muak dengan mereka seperti saya

@Yuudaari Saya akan menunjukkan bahwa mengetik Lodash's _.flow , Ramda's _.compose , dll. adalah salah satu hal yang mendorong bug ini, dan pengetikan yang berhasil adalah bagian dari pemecahan masalah ini. Faktanya, itulah salah satu alasan yang tercantum dalam deskripsi masalah asli.

Sungguh, pada titik ini, saya berpendapat 99% dari masalah yang ada saat ini dengan variadik adalah dengan ergonomi, bukan fungsionalitas. Kita dapat mengetikkan Function.prototype.bind dan Promise.all sempurna dengan campuran tipe terindeks, tipe kondisional, dan rekursi (Anda dapat melakukan Append pengulangan daftar untuk Function.prototype.bind , dan Promise.all akan menjadi iterasi sederhana + Append ), hanya saja sangat canggung dan boilerplatey untuk melakukannya.

Tidak mencoba menambah kebisingan di sini, hanya menjelaskan bahwa hal-hal yang dimulai di sini secara teknis sesuai topik karena menyangkut beberapa alasan bug itu ada, bahkan jika itu bukan yang Anda khawatirkan secara pribadi.

Saya pikir orang-orang yang menunggu pengumuman di sini melewatkan berita besar -- ternyata Concat<T, U> sekarang mungkin berfungsi persis seperti [...T, ...U] .

Sub-utas Pipe adalah tentang mendemonstrasikan fungsionalitas yang kami minta di sini. Ini tentang mencapai titik utas ini, hari ini.

Saya pikir itu berarti kita tidak akan lebih buruk dari menutup utas ini, jadi mungkin ini saat yang tepat untuk bertanya -- apa yang masih diinginkan orang dari proposal ini?

[itu] hanya saja sangat canggung dan boilerplatey

Sebagian besar tipe yang menggunakan ini akan menggunakan rekursi sendiri, sehingga mereka yang menulisnya pasti akan terbiasa dengannya, sementara pengguna akhir kemungkinan hanya akan menggunakan perpustakaan dengan tipe yang telah ditentukan dan menulis front-end mereka, tanpa harus mengetahui iterasi TS ada.

Pada saat itu mungkin proposal ini sebagian besar dapat meningkatkan kinerja?

Pertama, apakah menggunakan objek peta untuk mengelabui sistem tipe agar melakukan rekursi bahkan dimaksudkan? Tampaknya cukup hacky bagi saya. Jika saya menggunakan fungsi seperti itu (saya, tapi itu tidak relevan), apakah itu tidak akan rusak nanti?

Kedua, menggunakan solusi ini tidak... ramah. Itu tidak terlalu mudah dibaca (terutama bagi mereka yang tidak menulisnya), dan akibatnya terlihat menyedihkan untuk dipertahankan.

Mengapa saya ingin kembali pada proposal yang menambahkan fitur yang sama, dengan cara yang dimaksudkan, dapat dibaca, dan dipelihara, hanya karena ada solusi untuk mereka?

Saya tidak berpikir keberadaan solusi ini menyebabkan proposal ini dianggap sebagai gula sintaksis, tetapi meskipun demikian, mengapa saya tidak menginginkan gula sintaksis untuk kekacauan ini?

@Yuudaari

Sunting: Tambahkan tautan untuk konteks.

Pertama, apakah menggunakan objek peta untuk mengelabui sistem tipe agar melakukan rekursi bahkan dimaksudkan? Tampaknya cukup hacky bagi saya. Jika saya menggunakan fungsi seperti itu (saya, tapi itu tidak relevan), apakah itu tidak akan rusak nanti?

Lihatlah bug yang baru-baru ini saya ajukan: #26980. Anda tidak sendirian dalam mempertanyakan polanya. Ini agak di luar topik di sini, tetapi jangan ragu untuk ikut campur di sana.

Perhatikan ada sedikit matematika yang terlibat dalam cara mencari tahu apakah sesuatu rekursif berakhir (salah satu alasan utama mengapa itu sangat bernuansa).

Kedua, menggunakan solusi ini tidak... ramah. Itu tidak terlalu mudah dibaca (terutama bagi mereka yang tidak menulisnya), dan akibatnya terlihat menyedihkan untuk dipertahankan.

Mengapa saya ingin kembali pada proposal yang menambahkan fitur yang sama, dengan cara yang dimaksudkan, dapat dibaca, dan dipelihara, hanya karena ada solusi untuk mereka?

Saya tidak berpikir keberadaan solusi ini menyebabkan proposal ini dianggap sebagai gula sintaksis, tetapi meskipun demikian, mengapa saya tidak menginginkan gula sintaksis untuk kekacauan ini?

Memang ada cara yang disederhanakan untuk mengulangi tupel dalam kasus umum dari apa yang secara efektif Array.prototype.map , tetapi itu pada dasarnya tidak berguna untuk kebutuhan saya (saya membutuhkan akumulator).

Saya pribadi ingin gula sintaksis untuk ini:

  1. Menggabungkan dua daftar melalui [...First, ...Second] .
  2. Menambahkan nilai melalui [...Values, Item] .
  3. Mengekstrak elemen terakhir melalui T extends [...any[], infer Last] .
  4. Mengekstrak ekor melalui T extends [A, B, ...infer Tail] .

Gabungkan itu dengan #26980, dan saya bisa mengubah tipe di atas menjadi ini:

type Compose<L extends any[], V, R extends any[] = []> =
    L extends [infer H, ...infer T] ?
        Compose<T, H, [...R, (x: V) => H]> :
        R;

export type PipeFunc<T extends any[], V> =
    T extends [...any[], infer R] ?
        (...f: Compose<T, V>) => ((x: V) => R) :
        () => (x: V) => V;

Tapi itu saja. Saya tidak melihat banyak kegunaan untuk gula sintaksis lainnya, karena hampir semua yang ada di sini hanya berurusan dengan tupel, dan objek sebagian besar sudah memiliki semua yang Anda perlukan untuk operasi serupa.

Pertama, apakah menggunakan objek peta untuk mengelabui sistem tipe agar melakukan rekursi bahkan dimaksudkan? Tampaknya cukup hacky bagi saya. Jika saya menggunakan fungsi seperti itu (saya, tapi itu tidak relevan), apakah itu tidak akan rusak nanti?

Saya pikir kata resminya adalah seperti "jangan lakukan". @ahejlsberg berkata :

Ini pintar, tapi itu pasti mendorong hal-hal jauh melampaui penggunaan yang dimaksudkan. Meskipun mungkin berhasil untuk contoh kecil, itu akan berskala mengerikan. Menyelesaikan tipe yang sangat rekursif menghabiskan banyak waktu dan sumber daya dan mungkin di masa depan akan bertabrakan dengan gubernur rekursi yang kita miliki di pemeriksa.

Jangan lakukan itu!

@jcalz Jadi semakin banyak alasan untuk # 26980 ada?

Ketika saya mulai menggunakan TS pada akhir tahun ini, kecenderungan saya adalah untuk menulis _just that_! ( ...T ) dengan harapan itu akan menjadi sintaks untuk tupel typevar variadic. Nah, semoga ini masuk :)

Baru saja menemukan penggunaan baru untuk [...T, ...U] : mengetik pembuat HTML dengan benar. Untuk contoh konkret, anak-anak <video> harus sebagai berikut:

  • Jika elemen memiliki atribut src :

    • Nol atau lebih elemen <track>

  • Jika elemen tidak memiliki atribut src :

    • Nol atau lebih elemen <source>

  • Nol atau lebih elemen sesuai dengan model konten induk, kecuali tidak ada elemen turunan audio atau video yang diizinkan.

Ini pada dasarnya sama dengan tipe ini, tetapi tidak ada cara untuk mengekspresikannya dalam TypeScript hari ini:

type VideoChildren<ParentModel extends string[]> = [
    ...Array<"track">, // Not possible
    ...{[I in keyof ParentModel]: P[I] extends "audio" | "video" ? never : P[I]},
]

3,5 tahun :/

Kasus penggunaan:

type DrawOp<...T> = (G: CanvasRenderingContext2D, frame: Bounds, ...args: any[]) => void;
const drawOps: DrawOp<...any>[] = [];

function addDrawOp<...T>(fn: DrawOp<...T>, ...args: T) {
    drawOps.push(fn);
}

Saya hanya melihat kelebihan yang disebutkan secara singkat di bagian pertanyaan terbuka dari proposal, tetapi pasti sesuatu yang saya temui dan akan sangat baik untuk melihat solusi atau proposal, misalnya:

  function $findOne(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    cb: Cb<TSchema>,
  ): void;
  function $findOne<T extends keyof TSchema>(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    projection: Projection<T>,
    cb: Cb<Pick<TSchema, T>>,
  ): void;
  function $findOne(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    projection: undefined,
    cb: Cb<TSchema>,
  ): void;
  function $findOne<T extends keyof TSchema>(
    ctx: ICtx,
    filter: mongodb.FilterQuery<TSchema>,
    projection: Projection<T> | Cb<TSchema> | undefined,
    cb?: Cb<Pick<TSchema, T>>,
  ): void {

  promisify($findOne) // this can't infer types correctly

saat ini ini tidak berfungsi sama sekali dan cukup ketik promisify sebagai (ctx: ICtx, filter: FilterQuery<TSchema>) => Promise<TSchema[]> yang kehilangan informasi dari tanda tangan ini.

AFAICT satu-satunya solusi nyata untuk ini pada dasarnya adalah membuat fungsi janji dan secara manual menentukan semua jenis yang mungkin untuk varian itu - satu-satunya cara untuk benar-benar memberikan tanda tangan terhormat dari varian yang dibungkus adalah dengan tidak menentukan kelebihan dan hanya menentukan tanda tangan implementasi, tetapi tidak ada cara untuk memberi tahu penelepon jenis pengembalian mana yang harus mereka harapkan berdasarkan argumen yang mereka berikan jika Anda menentukan tanda tangan dengan cara itu.

ini diperburuk oleh fakta bahwa parameter sisanya hanya dapat menjadi parameter terakhir (yaitu (cb, ...args) valid tetapi tidak (...args, cb) , jadi meskipun tanda tangan secara internal adalah tipe gabungan, Anda sebenarnya tidak dapat sebarkan hal-hal dengan benar - misalnya akan cukup mudah jika cb selalu menjadi argumen pertama untuk mengetik promisify sebagai function promisify<T, V extends any[]>(fn: (cb: (err: Error | null, res?: T) => void, ...args: V)): (...args: V) => T dan Anda setidaknya bisa mendapatkan jenis serikat untuk tanda tangan dengan respons pengembalian yang sama, tetapi karena itu parameter terakhir karena tidak banyak yang bisa dilakukan di sini

@Qix- Skenario Anda diaktifkan oleh #24897. Diimplementasikan di TS 3.0.

@ahejlsberg Aduh ! Luar biasa, terima kasih ️

Sudah lama menunggu ... Tapi mungkin untuk menulis jenis variadic hari ini. TS cukup dewasa untuk menulis tipe kompleks yang berfungsi. Jadi saya meluangkan waktu untuk menulis jenis untuk kari, concat , menulis dan pipa untuk Ramda.

Dan sekarang dikirimkan dengan ts-toolbelt .

Namun, proposal ini adalah gula sintaksis yang bagus untuk membuat manipulasi tupel umum menjadi lebih mudah.

sudah ada di medium.com? URL?

Ada artikel asli di Medium tetapi bonus tidak termasuk di dalamnya, ada di repo. Ini juga menjelaskan bagaimana saya membuat semua alat kecil untuk menulis, menyalurkan & kari :senyum:

@pirix-gh tapi ini bukan obat generik variadik seperti dalam proposal ini

declare function m<...T>(): T

m<number, string>() // [number, string]

@goodmind Ya, tidak, itu lebih ditiru. Jadi Anda bisa meniru ... seperti ini:

declare function m<T extends any[], U extends any[]>(): Concat<T, U>

m<[number, string], [object, any]>() // [number, string, object, any]

Sama dengan:

declare function m<...T, ...U>(): [...T, ...U]

m<number, string, object, any>() // [number, string, object, any]

Sementara itu, sambil menunggu proposal ini :hourglass_flowing_sand:

@pirix-gh dapatkah Anda membantu dengan fungsi pembungkus seperti

type fn = <T>(arg: () => T) => T
let test1: fn
let res1 = test1(() => true) // boolean

type fnWrap = (...arg: Parameters<fn>) => ReturnType<fn>
let test2: fnWrap
let res2 = test2(() => true) // {}

Saya mencoba menggunakan pendekatan penulisan tetapi gagal, bisakah Anda menyarankan cara yang tepat?

Ini terjadi karena ketika Anda mengekstrak parameter/pengembalian fn yang bergantung pada obat generik, TS menyimpulkannya ke tipe terdekatnya (dalam hal ini T akan menjadi any ). Jadi tidak ada cara untuk melakukannya saat ini. Harapan terbaik kami adalah menunggu proposal ini digabungkan dengan https://github.com/Microsoft/TypeScript/pull/30215. Jadi, Anda harus menulis kelebihan jenis.

Atau, mungkin kami dapat menemukan cara untuk mempertahankan/memindahkan obat generik sedemikian rupa sehingga kami dapat melakukan:

declare function ideal<...T>(a: T[0], b: T[1], c: T[2]): T

ideal('a', 1, {}) // T = ['a', 1, {}]

Dengan cara ini, kita akan merekonstruksi fn dari potongan-potongannya. Bagian yang hilang hari ini adalah bagian umum seperti yang ditunjukkan @goodmind .

@pirix-gh Jika saya tidak salah, Anda bisa melakukan ini untuk mencapai apa yang Anda miliki di sana:

declare function MyFunction<A, B, C, Args extends [A, B, C]>(...[a, b, c]: Args): Args

const a = MyFunction(1, 'hello', true);
// typeof a = [number, string, boolean]

@ClickerMonkey Tidak persis, karena apa yang saya usulkan berfungsi untuk jumlah argumen yang tidak terbatas. Tapi mungkin kita juga bisa melakukan ini, dengan apa yang Anda usulkan (saya belum melihatnya di proposal):

declare function MyFunction<A, B, C, ...Args>(...[a, b, c]: Args): Args

const a = MyFunction(1, 'hello', true);
// typeof a = [number, string, boolean]

@pirix-gh Argumen tipe A , B dan C dalam contoh Anda tidak digunakan.

-declare function MyFunction<A, B, C, ...Args>(...[a, b, c]: Args): Args
+declare function MyFunction<...Args>(...[a, b, c]: Args): Args

Bahkan jika tipe variadik diimplementasikan, dua contoh sebelumnya mungkin akan menghasilkan kesalahan kompilasi, apa gunanya tipe variadik jika Anda hanya menginginkan tiga argumen.

Contoh Anda harus menunjukkan mengapa variadic diperlukan, jika dapat dilakukan dengan kode TS yang ada maka itu tidak membantu penyebabnya sama sekali.

@goodmind Ya, tidak, itu lebih ditiru. Jadi Anda bisa meniru ... seperti ini:

declare function m<T extends any[], U extends any[]>(): Concat<T, U>

m<[number, string], [object, any]>() // [number, string, object, any]

Sama dengan:

declare function m<...T, ...U>(): [...T, ...U]

m<number, string, object, any>() // [number, string, object, any]

Sementara itu, sambil menunggu proposal ini

Dari mana Anda mendapatkan Concat<> ?

Sunting: Sudahlah menemukan kode sumber.

@pirix-gh jadi saya mencoba melakukan ini dengan saran Anda tetapi tidak dapat menemukannya.

~Masalahnya adalah saya mencoba untuk memperluas parameter ctor dari suatu kelas dan berfungsi sampai-sampai saya memiliki array tipe tetapi saya tidak dapat menyebarkannya untuk params ctor.~

Class Test {
  constructor(x: number, y: string) {}
}
let ExtendedClass = extendCtor<[number, string], [number]>(Test);

let instance = new ExtendedClass(1, '22', 2);

Pembaruan: Sudahlah itu juga bekerja dengan menggunakan spread di fungsi ctor.

Ini link solusinya

Satu-satunya masalah adalah TS crash hampir setiap saat :|
dan inilah yang dikatakan TypeScript Type instantiation is excessively deep and possibly infinite.ts(2589)

Pembaruan 2:
Saya mencapainya dengan meletakkan tipe baru di awal, tetap saja bagus untuk dapat menggabungkan tipe-tipe ini.

// ...
type CtorArgs<T, X> = T extends (new (...args: infer U) => any) ? [...U, X] : never;
// To be used as CtorArgs<typeof Test, string>
// ...
let instance = new MyClass1('22', 2, 'check');

sebagai lawan:

let MyClass1 = extendClass<typeof Test, string>(Test);

let instance = new MyClass1('check', '22', 2);

Tautan ke solusi akhir.

Jika saya mengerti dengan benar Object.assign dapat dideklarasikan seperti berikut ini untuk sepenuhnya mendukung argumen variadic.

type Assign<T, U extends any[]> = {
  0: T;
  1: ((...t: U) => any) extends ((head: infer Head, ...tail: infer Tail) => any)
    ? Assign<Omit<T, keyof Head> & Head, Tail>
    : never;
}[U['length'] extends 0 ? 0 : 1]

interface ObjectConstructor {
  assign<T, U extends any[]>(target: T, ...source: U): Assign<T, U>
}

Apakah ada alasan itu dideklarasikan dengan cara yang berbeda di lib.d.ts TypeScript?

Itu tidak menggunakan tipe kondisional rekursif karena itu tidak didukung (lihat #26980 untuk diskusi tentang itu, atau komentar ini memberi tahu kami untuk tidak melakukan itu). Jika seseorang mau menggunakan tipe pengembalian persimpangan saat ini ada #28323.

@jcalz Saya membuat tes berat yang menunjukkan jenis Minus beraksi. Ia melakukan Minus 216000 kali dalam waktu kurang dari 4 detik. Ini menunjukkan bahwa TS dapat menangani tipe rekursif dengan sangat baik. Tapi ini cukup baru.

Mengapa? Ini berkat Anders :tada: (https://github.com/microsoft/TypeScript/pull/30769). Dia mengizinkan saya untuk beralih dari tipe kondisional ke kondisi terindeks (seperti sakelar). Dan faktanya, ini meningkatkan kinerja x6 untuk ts-toolbelt. Begitu banyak, banyak terima kasih padanya.

Jadi secara teknis, kita bisa menulis ulang tipe

import {O, I, T} from 'ts-toolbelt'

// It works with the same principles `Minus` uses
type Assign<O extends object, Os extends object[], I extends I.Iteration = I.IterationOf<'0'>> = {
    0: Assign<O.Merge<Os[I.Pos<I>], O>, Os, I.Next<I>>
    1: O
}[
    I.Pos<I> extends T.Length<Os>  
    ? 1
    : 0
]

type test0 = Assign<{i: number}, [
    {a: '1', b: '0'},
    {a: '2'},
    {a: '3', c: '4'},
]>

Lib juga membuat rekursi aman dengan Iteration yang akan mencegah overflow dari TypeScript. Dengan kata lain, jika I melampaui 40 maka itu meluap dan Pos<I> sama dengan number . Dengan demikian menghentikan rekursi dengan aman.

Jenis rekursif serupa yang saya tulis ( Curry ) dikirimkan bersama Ramda , dan sepertinya

Omong-omong, saya berterima kasih kepada Anda (@jcalz) di halaman proyek untuk semua saran bagus Anda.

Saya tidak yakin apakah #5453 adalah tempat terbaik untuk berdiskusi... haruskah kita membicarakan ini di #26980 atau adakah lokasi yang lebih kanonik? Dalam hal apapun saya akan senang untuk memiliki cara resmi dan didukung untuk melakukan hal ini yang tidak akan mungkin meledak pada rilis berikutnya dari naskah. Sesuatu yang termasuk dalam tes dasar mereka sehingga jika rusak mereka akan memperbaikinya. Bahkan jika kinerjanya diuji dengan baik, saya akan berhati-hati melakukan ini di lingkungan produksi apa pun tanpa kata resmi dari seseorang seperti @ahejlsberg.

Sesuatu yang termasuk dalam tes dasar mereka sehingga jika rusak mereka akan memperbaikinya.

Saya pikir kami memiliki sesuatu yang cukup dekat digunakan secara internal

@weswigham maafkan saya karena padat, tetapi dapatkah Anda menunjukkan kepada saya bagaimana tipe yang disorot adalah rekursif? Hal yang aku khawatirkan adalah bentuknya

type Foo<T> = { a: Foo<Bar<T>>, b: Baz }[Qux<T> extends Quux ? "a" : "b" ]

atau salah satu varian yang pernah saya lihat. Jika saya kehilangan sesuatu dan ini telah diberi semacam lampu hijau, tolong seseorang beri tahu saya (dan ajari saya cara menggunakannya!)

Oh, adil - itu berbeda dalam hal itu, ya. Saya hanya mengatakan pola "objek yang diindeks segera untuk memilih jenis" dan menyadari bahwa kami memiliki _that_.

Saya punya pertanyaan.

Berapa banyak hal yang diusulkan di sini yang masih relevan? Masalah ini dibuka 4 tahun yang lalu dan saya merasa banyak hal telah berubah sejak saat itu.

Dari komentar saya di sini,
https://github.com/microsoft/TypeScript/issues/33778#issuecomment -537877613

Saya bilang,

TL; DR, tipe tuple, argumen istirahat, tipe array yang dipetakan, inferensi tupel untuk argumen non-istirahat, alias tipe rekursif = tidak ada kebutuhan nyata untuk dukungan argumen tipe variabel

Tetapi saya ingin tahu apakah ada yang memiliki kasus penggunaan yang tidak dapat diaktifkan oleh alat yang ada

Sampai kita mendapatkan versi resmi dari Concat<T extends any[], U extends any[]> maka ini masih relevan. Saya tidak berpikir fitur referensi tipe rekursif yang akan datang memberikan ini kepada kami, tetapi saya akan senang (secara resmi) diberitahu sebaliknya.

Bukankah kita sudah memiliki implementasi Concat<> ?

Atau apakah frasa kunci di sini "diberkati secara resmi"?

Karena pernyataan saya adalah bahwa pada dasarnya Anda dapat melakukan semua (atau hampir semua hal?) yang Anda inginkan saat ini, bahkan jika itu tidak cukup "diberkati secara resmi".

Tapi saya kira "resmi diberkati" harus selalu lebih disukai ... Poin bagus. Saya terlalu terbiasa (ab) menggunakan alias tipe rekursif itu

Saya biasanya lebih suka sintaks yang nyata dan elegan sehingga setiap kali saya melakukan sesuatu seperti ini, saya tidak harus terus menjelaskan kepada rekan tim saya (seringkali junior) apa yang terjadi untuk tipe spesifik yang membingungkan yang diperlukan oleh penyalahgunaan status-quo. Kebingungan itu merusak kemampuan saya untuk menginjili TypeScript, atau setidaknya penggunaan ini, di org.

Besar 👍 ini!

Fitur ini SANGAT penting.

@AnyhowStep

Karena pernyataan saya adalah bahwa pada dasarnya Anda dapat melakukan semua (atau hampir semua hal?) yang Anda inginkan saat ini

Saya tidak melihat bukti bahwa mengetik param spread dengan beberapa param tunggal di sampingnya mudah dicapai hari ini di TS.

@matthew-dean tidak sepenuhnya benar. Berikut adalah contoh yang dapat Anda capai sampai batas tertentu.

Seperti yang saya pahami, TS berusaha mengetik sebanyak mungkin program vanilla JS. Berikut adalah teka-teki:

const f = <T extends any[]>(...args: T): T => args;
const g = <T extends any[]>(...a: T): WhatExactly<T> => {
    return f(3, ...a, 4, ...a, 5);
}
g(1, 2);

Saya berharap jenisnya tidak lebih kompleks dari [number, ...T, number, ...T, number] . Jika kita harus menulis 20 baris dari beberapa kode aneh yang menyalahgunakan bug yang terjadi, periksa untuk memiliki tipe pengembalian yang tepat di baris terakhir, masalah ini tidak teratasi.

@polkovnikov-ph Jenis saat ini disimpulkan sebagai: [number, ...any[]] , yang tidak membantu.

Saya juga ingin memperhatikan bahwa kita tidak harus menghormati aturan kesepuluh Greenspun selama 15 tahun seperti yang dilakukan C++, karena C++ sudah melalui semua Head<> s dan Cons<> untuk kita, dan merancang beberapa sintaks template variadic yang sangat berguna dan bersih. Kami dapat menghemat (ratusan tahun) waktu pengembang dan hanya mengambil bagian terbaik dari sana.

Misalnya, jenis variadic memiliki yang berbeda jenis di C ++, sehingga Anda tidak dapat menggunakan variabel jenis variadic di mana jenis ini diharapkan, tidak seperti tipe yang extends any[] di TS. Ini memungkinkan C++ untuk memetakan/Zip di atas tupel dengan menyebutkan variabel tipe variadik di dalam beberapa ekspresi yang terlampir dalam operator elipsis. Ini cukup banyak alternatif Tuple dari tipe objek yang dipetakan.

type Somethify<...T> = [...Smth<T>]
type Test1 = Somethify<[1, 2]> // [Smth<1>, Smth<2>]

type Zip<...T, ...U> = [...[T, U]]
type Test2 = Zip<[1, 2], [3, 4]> // [[1, 3], [2, 4]]

type Flatten<...T extends any[]> = [......T]
type Test3 = Flatten<[[1, 2], [3, 4]]> // [1, 2, 3, 4]

Harap sebutkan bahwa sintaks elipsis yang diusulkan alih-alih extends any[] digunakan dalam contoh bukan hanya karena alasan estetika, tetapi karena

type A<T> = any[]
type B<T extends any[]> = [...A<T>]
type C = B<[1, 2]>

sudah menjadi program TS yang valid. C akhirnya menjadi any[] bukannya [any[], any[]] yang akan dihasilkan oleh tipe variadik yang dipetakan.

@DanielRosenwasser Saya minta maaf karena melakukan ping kepada Anda seperti ini, tetapi saya ingin memunculkan kembali masalah ini untuk meningkatkan kemungkinan mendapat cinta. Jenis variadik akan sangat membantu, meskipun saya menyadari menerapkannya adalah tugas besar!

Selain itu, hanya memiliki operasi penyebaran tingkat tipe untuk tipe Tuple akan sangat membantu tim saya, bahkan jika kurangnya jenis variadik berarti mereka tidak dapat digunakan dengan parameter tipe. Dalam domain masalah kami "array dengan beberapa struktur" sangat umum. Ini akan sangat menyederhanakan banyak hal bagi kami jika operasi ini berhasil:

type SharedValues = [S1, S2, S3];
type TupleOfSpecificKind = [V1, ...SharedValues, V2];

@sethfowler jika Anda memiliki beberapa contoh dari apa yang ingin Anda ungkapkan, itu selalu membantu kami. Kalau tidak, Anda mungkin tertarik dengan https://github.com/microsoft/TypeScript/issues/26113

@DanielRosenwasser Tentu, saya bisa membuat segalanya sedikit lebih konkret. Saya akan menjaga seluk-beluknya, tetapi pada tingkat tinggi Anda dapat menganggap proyek kami sebagai menghasilkan aliran operasi grafis dan peristiwa serupa lainnya yang dikirim ke server jauh. Untuk alasan efisiensi, kita perlu merepresentasikan operasi tersebut dalam memori dalam format yang dapat diterjemahkan secara langsung ke dalam bentuk serialnya. Jenis untuk acara ini akhirnya terlihat seperti ini:

type OpLineSegment = [
  StrokeColor,
  FillColor,
  number,  // thickness
  number, number, number,  // X0, Y0, Z0
  number, number, number  // X1, Y1, Z1
];
type OpCircle = [
  StrokeColor,
  FillColor,
  number, number, number,  // X, Y, Z of center
  number // radius
];
type OpPolygon = (StrokeColor | FillColor | number)[];  // [StrokeColor, FillColor, repeated X, Y, Z]]
type OpFan = (StrokeColor | FillColor | number)[];  // StrokeColor, FillColor, repeated X, Y, Z up to 10x

Kami ingin dapat mengekspresikan jenis ini lebih seperti ini:

type Colors = [StrokeColor, FillColor];
type Vertex3D = [number, number, number];

type OpLineSegment = [...Colors, number /* thickness */, ...Vertex3D, ...Vertex3D];
type OpCircle = [...Colors, ...Vertex3D, number /* radius */];
type OpPolygon = [...Colors, ...Repeated<...Vertex3D>];
type OpFan = [...Colors, ...RepeatedUpToTimes<10, ...Vertex3D>];

Kami memiliki sejumlah besar perintah ini, jadi hanya memiliki penyebaran tingkat tipe akan menghasilkan kode yang lebih dapat dipelihara secara dramatis. Memiliki jenis variadik sehingga kita dapat menulis fungsi tingkat tipe seperti Repeated<> dan RepeatedUpToTimes<> (yang akan mengevaluasi ke penyatuan tipe tuple yang ditentukan secara rekursif dalam contoh ini) akan melangkah lebih jauh untuk menyederhanakan banyak hal.

Ini juga akan sangat berguna untuk memiliki dukungan untuk hal-hal seperti rangkaian tipe-aman dari tipe Tuple (seperti yang dibahas dalam OP). Untuk menggunakan tipe di atas, saat ini kita harus membangun seluruh tupel dalam ekspresi literal tupel tunggal. Kami tidak dapat membangunnya menjadi beberapa bagian dan menggabungkannya sekarang. Dengan kata lain, operasi di bawah ini tidak bekerja hari ini, tetapi kami sangat berharap mereka melakukannya.

const colors: Colors = getColors();
const center: Vertex3D = getCenter();

// Doesn't work! Produces a homogenous array.
const circle1: OpCircle = [...colors, ...center, radius];

// Doesn't work; can't write this function today.
const circle2: OpCircle = concat(colors, center, radius);

// We need to do this today; it's quite painful with more complex tuple types.
const circle3: OpCircle = [colors[0], colors[1], center[0], center[1], center[2], radius];

Semoga contoh-contoh ini bermanfaat!

Anda dapat dengan mudah menulis tipe Concat<> , dan membuat tipe Concat3<> , menggunakan Concat<> .

Kemudian,

type OpCircle = Concat3<Colors, Vertex3D, [number] /* radius */>;

Dari atas, Anda dapat menulis fungsi concat dengan kelebihan beban untuk 2,3,4,5,6, dll. sejumlah argumen.

Bahkan dimungkinkan untuk menulis impl Concat<> yang mengambil tupel dari tupel dan menggabungkan tupel. Tipe var-arg Concat<>.


Itu bukan hal yang tidak bisa dilakukan hari ini. Itu bisa dilakukan, bahkan jika itu mengharuskan Anda untuk menulis tipe rekursif, dan fungsi pembantu.

Setiap kali saya menggunakan tipe rekursif itu di vscode, TS ingin mematikan cpu atau hang! Itu masalah utamanya, saya merasa TS terlalu berat tanpa alasan yang jelas.

Mungkin orang yang menulis jenisnya tidak cukup melakukan untuk mengoptimalkannya?

Saya tidak ingin melakukan spam atau menyeret dalam percakapan lama yang tidak akan menambah banyak nilai pada utas ini, tetapi saya ingat pada suatu saat mendengar bahwa perhitungan tipe kondisional rekursif mahal dalam javascript (saya menemukannya di sini di utas ini )

Yang mengatakan (saya tahu ini mungkin terdengar gila,) mungkin sudah waktunya untuk menulis ulang TS dalam bahasa lain untuk dapat memberikan dorongan pada sistem tipe karena TS sudah berkembang sedemikian rupa sehingga masuk akal untuk meminta yang lebih baik.

Anda dapat dengan mudah menulis tipe Concat<> , dan membuat tipe Concat3<> , menggunakan Concat<> .

Bisakah Anda memberikan implementasi untuk tipe Concat<> Anda gambarkan? Sangat mudah untuk menulis Cons<> , tetapi Concat<> tidak begitu mudah (bagi saya) dan saya ingin melihat apa yang Anda bayangkan.

Mengenai Concat3<> , Concat4<> , dll, harapannya dalam jangka panjang kita tidak perlu menulis puluhan varian seperti ini, karena kita akan memiliki jenis variadik. Namun, jika implementasi yang baik dari mereka dimungkinkan hari ini, itu akan menjadi tindakan sementara yang masuk akal.

Untuk penggabungan reguler dari dua tupel,
https://github.com/AnyhowStep/ts-trampoline-test (menggunakan trampolin untuk Concat tupel yang sangat besar, yang kebanyakan orang tidak perlu)

Concat3 hanya akan menjadi Concat, C>

VarArgConcat akan menjadi,
VarArgConcat<TuplesT extends readonly (readonly unknown[])[], ResultT extends readonly unknown[] = []>

Sementara Tuple tidak kosong, VargArgConcat<PopFront<TuplesT>, Concat<ResultT, TuplesT[0]>>

Jika TuplesT kosong, kembalikan ResultT

Tentu saja, rekursi naif akan menyebabkan kesalahan kedalaman maksimal dengan tupel dengan panjang yang layak. Jadi, baik menggunakan teknik rekursi di ts-toolbelt, atau menggunakan trampolin dengan copy-paste ke kedalaman yang diinginkan


Repo yang saya tautkan menggunakan Reverse<> untuk mengimplementasikan Concat<> . Saya menyalin dan menempelkan kode dari proyek lain yang sedang saya kerjakan.

Saya setuju bahwa ini akan menjadi fitur yang sangat berguna.

Katakanlah kita memiliki tipe T :

type T = {
  tags: ["a", "b", "c"];
};

Dan kami ingin membuat tipe baru, dengan tag tambahan "d" , ditambahkan ke tuple T["tags"] . Pengguna mungkin awalnya mencoba dan membuat utilitas ini ( WithTag<NewTag, ApplyTo> ) sebagai berikut:

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
  tags: [Tag, ...Target["tags"]];
};

Mencoba ini saat ini menimbulkan kesalahan A rest element type must be an array type . Pengguna mungkin berpikir bahwa menukar string[] untuk Array<string> membuat perbedaan, tetapi ternyata tidak. Juga tidak menggunakan kondisi + never :

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
- tags: [Tag, ...Target["tags"]];
+ tags: Target["tags"] extends string[] ? [Tag, ...Target["tags"]] : never;
};

Playground Link: https://www.typescriptlang.org/play?#code/C4TwDgpgBA6glsAFgFQIYHMA8AoKU3pQQAewEAdgCYDOU1wATnOegDS76oPoTBGkUaUAN7AM1AFx1GzdAG0AugF9sAPigBeTt15QAZCI5j0kqHIKsoAOhtodwOQCJj1RwoUBubEq -ZQkKABJTUM8FyknVEdLRwAjaKhHAGM3Lx9sP3BoZBD4JAJMR0oEwNVfAHoAKkrcSqgAUWJIJLJKKAADZHaoYAB7KFjoXoAzHsRoYd6AGynegHdZHqyrWqhV4VWe8QjHKJj4mJSY4s9VlShK8qA

Masalah Terkait: :

Sayangnya, strategi ini tidak berfungsi untuk parameter istirahat. Ini baru saja diubah menjadi array:

function i(a: number, b?: string, ...c: boolean[]): number {
}
let curried = curry(i, 12);
curried('foo', [true, false]);
curried([true, false]);

Di sini, kari: ...([string, boolean[]] | [boolean[]]) => number .
Saya pikir ini dapat didukung jika ada kasus khusus untuk fungsi dengan parameter istirahat Tuple, di mana elemen terakhir dari Tuple adalah array.
Dalam hal ini pemanggilan fungsi akan memungkinkan argumen tambahan dari tipe yang benar untuk mencocokkan array.
Namun, itu tampaknya terlalu rumit untuk menjadi berharga.

Ini memiliki dua masalah:

  1. Kode tidak benar. curried() tidak menerima array. Mengisi parameter rest c dengan [true, false] dapat dilakukan oleh curried('foo', ...[true, false]) tetapi ini akan gagal dalam TypeScript dengan saran ini. Kami mungkin tidak dapat memberikan solusi pengetikan untuk beberapa kasus, tetapi tidak disarankan untuk memberikan seseorang yang salah!
  2. Secara tidak sengaja, Anda menggabungkan parameter opsional dan sisanya, dan mengungkapkan bug dengan proposal Anda. curried() tidak dapat dipanggil tanpa b tetapi dengan c . Melakukan hal itu akan menyebabkan perilaku buruk. TypeScript tahu curried() adalah (...items: [string, boolean[]] | [boolean[]]) tapi itu tidak benar . Karena JavaScript tanpa mengetik, meneruskan [true, false] ke c (dengan asumsi kita memecahkan masalah di atas) dengan curried([true, false]) tidak akan menyetel b ke undefined (atau nilai defaultnya) dan c menjadi [true, false] , tetapi akan menyetel b menjadi true dan c menjadi [false] !

Saya menyarankan perbaikan berikut:

  1. Untuk masalah kedua (dan lebih mudah), solusinya sederhana: jangan menyimpulkan tupel gabungan untuk argumen opsional terakhir (yaitu [number, string, boolean[]] | [number, boolean[]] dalam kasus kami) ketika ada parameter istirahat. Sebagai gantinya, simpulkan [number, string, boolean[]] | [number] - yaitu, satu kasus untuk tanda tangan penuh, termasuk semua opsional dan sisanya, satu untuk setiap opsional kecuali yang terakhir , dan satu tanpa yang terakhir dan yang lainnya.
  2. Masalah pertama lebih rumit: Anda sudah mengatakan bahwa Anda berpikir itu terlalu rumit untuk menjadi berharga. Saya pikir itu _is_ bermanfaat mengingat popularitas parameter istirahat, tetapi itu _perlu_ karena masalah pertama (kemenangan! ). Saya pikir akan lebih baik jika kita mengekspos antarmuka untuk Tuple-with-last-rest-array (saya berpikir tentang sintaks [t1, t2, t3, ...arr] ), tetapi kita tidak perlu melakukannya. Kami dapat tetap menggunakannya sebagai internal (haha, Anda masih harus berurusan dengan cara menampilkan tipe dalam IDE ).

Tapi setelah semua keluhan dan provokatif, proposal bagus! Terima kasih 👍 (hanya untuk menenangkan Anda, ini adalah masalah pertama di GitHub yang saya tanggapi dengan tiga emoji - 👍, dan ❤️).

Ini akan sangat berguna di Angular Injector yang saat ini harus menggunakan any https://github.com/angular/angular/issues/37264

Dalam contoh ini A, B, C dapat direpresentasikan sebagai tipe generik variadik ...A tunggal. Tapi saya tidak tahu bagaimana ini akan dipetakan ke sesuatu di mana setiap elemen generik variadic akan diapit dalam tipe lain ( Type ). mungkin dengan tipe pembantu? Atau haruskah sintaks mengizinkan sesuatu seperti ...Type<A> ?

export declare interface TypedFactoryProvider<T, A, B, C> {
    provide: Type<T | T[]> | InjectionToken<T | T[]>;
    multi?: boolean;
    useFactory: (a: A, b: B, c: C) => T;
    deps: [Type<A>, Type<B>, Type<C>];
}

(konteks: implementasi Provider akan menyuntikkan instance deps ke dalam fungsi pabrik dalam urutan itu. Pengetikan yang ketat akan memastikan bahwa pengembang mengetahui apa yang akan disuntikkan dan dalam urutan apa.)

Ketika ini selesai, harap ingat untuk memperbarui parameter kedua String.prototype.replace, sehingga akhirnya memiliki pengetikan yang tepat di TypeScript!

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter

@Griffork Anda menyadari bahwa akan memerlukan penguraian regex untuk mengetahui berapa banyak grup tangkapan yang dimilikinya, bukan?

Ini akan sangat berguna di Angular Injector yang saat ini harus menggunakan any angular/angular#37264

Dalam contoh ini A, B, C dapat direpresentasikan sebagai tipe generik variadik ...A tunggal. Tapi saya tidak tahu bagaimana ini akan dipetakan ke sesuatu di mana setiap elemen generik variadic akan diapit dalam tipe lain ( Type ). mungkin dengan tipe pembantu? Atau haruskah sintaks mengizinkan sesuatu seperti ...Type<A> ?

export declare interface TypedFactoryProvider<T, A, B, C> {
  provide: Type<T | T[]> | InjectionToken<T | T[]>;
  multi?: boolean;
  useFactory: (a: A, b: B, c: C) => T;
  deps: [Type<A>, Type<B>, Type<C>];
}

(konteks: implementasi Provider akan menyuntikkan instance deps ke dalam fungsi pabrik dalam urutan itu. Pengetikan yang ketat akan memastikan bahwa pengembang mengetahui apa yang akan disuntikkan dan dalam urutan apa.)

@AlexAegis

Saya merasa itu akan diketik seperti ini:

export declare interface TypedFactoryProvider<T, ...P> {
  provide: Type<T | T[]> | InjectionToken<T | T[]>;
  multi?: boolean;
  useFactory: (...providers: ...P) => T;
  deps: [...Type<P>];
}

Masalah ini sekarang diperbaiki oleh #39094, dijadwalkan untuk TS 4.0.

Jika ini datang dengan 4.0, sekarang kami memiliki alasan untuk menamakannya 4.0
Ini benar-benar fitur baru yang utama

Ini bagus! Hanya hal "kiri" yang sama untuk tipe string literal

@sandersn Saya mencoba memikirkan bagaimana sintaks ini digunakan dalam hal-hal seperti RxJS , di mana parameter metode pipe agak bergantung satu sama lain,

seperti pada pipe(map<T, V>(...), map<V, U>(...), filter(...), ...) . Bagaimana Anda mengetiknya dengan cara yang tidak mereka lakukan sekarang? (Puluhan baris dengan panjang variadik yang berbeda mengetik)

@gioragutt menggunakan PR yang dikirimkan @ahejlsberg Saya pikir ini akan berhasil tetapi saya bisa saja salah

type Last<T extends readonly unknown[]> = T extends readonly [...infer _, infer U] ? U : undefined;

interface UnaryFunction<T, R> { (source: T): R; }

type PipeParams<T, R extends unknown[]> = R extends readonly [infer U] ? [UnaryFunction<T, U>, ...PipeParams<R>] : [];

function pipe<T, R extends unknown[]>(...fns: PipeParams<T, R>): UnaryFunction<T, Last<R>>;

@tylorr Apakah tidak cukup bekerja, karena kesalahan tipe melingkar.

Namun, solusi yang biasa berhasil .

type Last<T extends readonly unknown[]> = T extends readonly [...infer _, infer U] ? U : undefined;

interface UnaryFunction<T, R> { (source: T): R; }

type PipeParams<T, R extends unknown[]> = {
    0: [],
    1: R extends readonly [infer U, ...infer V]
    ? [UnaryFunction<T, U>, ...PipeParams<U, V>]
    : never
}[R extends readonly [unknown] ? 1 : 0];

declare function pipe<T, R extends unknown[]>(...fns: PipeParams<T, R>): UnaryFunction<T, Last<R>>;

@isiahmeadows Sepertinya itu tidak berhasil untuk saya. 😢
Contoh taman bermain .

Saya mendapatkan sesuatu yang lebih dekat untuk bekerja tetapi itu tidak akan menyimpulkan jenisnya.
contoh taman bermain

aku harus berubah
R extends readonly [unknown] ? 1 : 0
ke
R extends readonly [infer _, ...infer __] ? 1 : 0

Tidak yakin mengapa

@tylorr @treybrisbane Mungkin terkait: https://github.com/microsoft/TypeScript/pull/39094#issuecomment -645730082

Juga, dalam kedua kasus tersebut, saya sangat merekomendasikan untuk membagikannya dalam permintaan tarik yang berisi komentar.

Jenis tuple variadik adalah tambahan yang luar biasa untuk bahasa ini, terima kasih atas usahanya!

Tampaknya, konstruksi seperti curry mungkin juga bermanfaat (baru saja diuji dengan staging playground ):

// curry with max. three nestable curried function calls (extendable)
declare function curry<T extends unknown[], R>(fn: (...ts: T) => R):
  <U extends unknown[]>(...args: SubTuple<U, T>) => ((...ts: T) => R) extends ((...args: [...U, ...infer V]) => R) ?
    V["length"] extends 0 ? R :
    <W extends unknown[]>(...args: SubTuple<W, V>) => ((...ts: V) => R) extends ((...args: [...W, ...infer X]) => R) ?
      X["length"] extends 0 ? R :
      <Y extends unknown[]>(...args: SubTuple<Y, X>) => ((...ts: X) => R) extends ((...args: [...Y, ...infer Z]) => R) ?
        Z["length"] extends 0 ? R : never
        : never
      : never
    : never

type SubTuple<T extends unknown[], U extends unknown[]> = {
  [K in keyof T]: Extract<keyof U, K> extends never ?
  never :
  T[K] extends U[Extract<keyof U, K>] ?
  T[K]
  : never
}

type T1 = SubTuple<[string], [string, number]> // [string]
type T2 = SubTuple<[string, number], [string]> // [string, never]

const fn = (a1: number, a2: string, a3: boolean) => 42

const curried31 = curry(fn)(3)("dlsajf")(true) // number
const curried32 = curry(fn)(3, "dlsajf")(true) // number
const curried33 = curry(fn)(3, "dlsajf", true) // number
const curried34 = curry(fn)(3, "dlsajf", "foo!11") // error

Fungsi generik tidak berfungsi dengan kari di atas.

Saya tidak percaya PR ini menyelesaikan masalah khusus ini.

Dengan PR ini berhasil

function foo<T extends any[]>(a: [...T]) {
  console.log(a)
}

foo<[number, string]>([12, '13']);

Tetapi masalah ini ingin melihat implementasi untuk ini sejauh yang saya lihat:

function bar<...T>(...b: ...T) {
  console.log(b)
}

bar<number, string>(12, '13');

Ada banyak kurung sudut di sana, terlihat sedikit berlebihan.

@AlexAegis Saya tidak yakin saya melihat banyak nilai dalam "parameter tipe istirahat" seperti itu. Anda sudah dapat melakukan ini:

declare function foo<T extends any[]>(...a: T): void;

foo(12, '13');  // Just have inference figure it out
foo<[number, string]>(12, '13');  // Expclitly, but no need to

Jangan berpikir kita benar-benar menginginkan konsep yang sama sekali baru (yaitu parameter tipe istirahat) hanya agar tanda kurung siku dapat dihindari dalam kasus yang jarang terjadi di mana inferensi tidak dapat mengetahuinya.

@ahejlsberg saya mengerti. Saya bertanya karena beberapa perpustakaan (RxJS seperti yang disebutkan) menggunakan solusi untuk menyediakan fungsionalitas ini. Tapi itu terbatas.

bar<T1>(t1: T1);
bar<T1, T2>(t1: T1, t2:T2);
bar<T1, T2, T3>(t1: T1, t2:T2, t3: T3, ...t: unknown) { ... }

Jadi sekarang mereka tetap dengan itu, atau meminta pengguna mengetikkan tanda kurung, yang merupakan perubahan besar, dan tidak seintuitif itu.

Alasan mengapa saya menggunakan contoh ini adalah karena di sini saya langsung mendefinisikan jenis Tuple itu. Satu braket persegi di sini, satu di sana

foo<[number, string]>([12, '13']);

Di sini tidak begitu jelas bahwa Tuple merujuk ke parameter istirahat itu jika Anda melihatnya dari luar

foo<[number, string]>(12, '13'); 

Tapi ya seperti yang Anda katakan jika kita membiarkan inferensi mengetahuinya, maka kasus-kasus sepele ini tidak memerlukan modifikasi apa pun dari pengguna. Tapi kami tidak tahu apakah mereka mengaturnya secara eksplisit atau tidak, itu terserah mereka, jadi itu masih dianggap sebagai perubahan besar. Tapi itu perhatian lib dan bukan perubahan ini.

Yang mengatakan saya hanya merasa aneh bahwa jika ada parameter istirahat, yang didefinisikan dari luar satu per satu, yaitu array tunggal di dalam yang dibedakan oleh ... , tidak dapat dibuat generik dengan cara yang sama: satu per satu di luar, array tunggal di dalam, dibedakan oleh ... .

Perbedaan sintaksis kecil tidak benar-benar sepadan dengan biaya dukungan untuk yang terpisah
jenis. Menggunakan jenis akan menjadi keputusan desain yang tepat ketika TS sedang merencanakan
dukungan untuk parameter istirahat, tapi saya kira sekarang mungkin mengarah ke lebih banyak
kebingungan baik untuk pengembang bahasa dan pengguna. Kami membutuhkan solusi untuk
masalah ini, dan Anders melakukan pekerjaannya dengan sangat baik menghindari itu
kompleksitas dengan tetap berpegang pada [...T] alih-alih T . Hormat!

(Bisakah sekarang kita melihat bug yang menyatukan tipe persimpangan untuk
variabel yang disimpulkan dalam tipe bersyarat mengembalikan tipe persimpangan paling kanan
argumen, atau penyatuan array itu bukan array penyatuan? Kita masih
memiliki showstoppers utama dalam sistem tipe.)

Pada Jumat, 19 Jun 2020, 10:41 Győri Sándor [email protected] menulis:

@ahejlsberg https://github.com/ahejlsberg Saya mengerti. Saya bertanya karena
beberapa perpustakaan (RxJS seperti yang disebutkan) menggunakan solusi untuk menyediakan ini
Kegunaan. Tapi itu terbatas.

batang(t1: T1);bar(t1: T1, t2:T2);bar(t1: T1, t2:T2, t3: T3, ...t: tidak diketahui) { ... }

Jadi sekarang mereka tetap dengan itu, atau meminta pengguna mengetikkan tanda kurung,
yang tidak begitu intuitif.

Alasan mengapa saya menggunakan contoh ini adalah karena ini sangat mudah
bahwa saya mendefinisikan jenis tupel itu. Satu braket persegi di sini, satu di sana

foo<[angka, string]>([12, '13']);

Di sini tidak begitu jelas bahwa Tuple merujuk ke parameter istirahat itu jika
kamu melihatnya dari luar

foo<[angka, string]>(12, '13');


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646490130 ,
atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/AAWYQIMTTB6JEPSQFUMTMDTRXMJD5ANCNFSM4BTBQ7DQ
.

Saya tentu saja tidak mendekati kalibernya, tetapi saya dengan hormat tidak setuju dengan @ahejlsberg .
Dalam pengalaman saya, sebagian besar kompleksitas TypeScript berasal dari fakta bahwa banyak (menarik dan berguna untuk memastikan) fitur yang dibuat khusus sebagai konsep mereka sendiri.

Kompleksitas ini pada dasarnya bukan fungsi dari jumlah fitur!
Alih-alih, bahasa dapat dirancang di sekitar konsep yang lebih besar dan lebih menyeluruh dari mana kasus-kasus khusus ini kemudian dapat disimpulkan secara sepele, atau diimplementasikan di perpustakaan std (tipe).

Konsep paling umum seperti itu tentu saja adalah untuk sepenuhnya mengimplementasikan tipe dependen, dari mana segala sesuatu yang lain kemudian dapat diturunkan, tetapi melangkah sejauh itu tidak diperlukan:
Seperti yang ditunjukkan C++ dan, pada tingkat lebih rendah, Rust telah menunjukkan, beberapa konsep skala besar dan konsisten memberi Anda banyak fitur gratis.
Ini mirip dengan apa yang OCaml dan Haskell (dan saya asumsikan F#?) telah dilakukan pada level nilai, hanya pada level tipe.

Pemrograman tingkat tipe tidak perlu ditakuti selama itu dirancang ke dalam bahasa alih-alih ditempelkan untuk menyediakan fitur-fitur tertentu.
Fasilitas di C++ 14/17 sangat intuitif kecuali sintaksnya, yang murni karena bagasi historis.

Konsep menyeluruh dapat ditambahkan dalam desain aslinya. Setelah desain
kesalahan sudah dibuat, konsistensi tidak dapat ditambahkan tanpa risiko besar
kembali-ketidakcocokan. Saya setuju dengan kecurigaan mengenai desain bahasa sebagai
keseluruhan (TS cukup jauh dari standar yang ditetapkan oleh akademisi, tidak ada yang bisa
tidak setuju dengan itu). Ada banyak bug dan inkonsistensi yang
dasar bagi jutaan basis kode produksi. Hanya fakta bahwa
pengembang dapat membuat tambahan yang berguna untuk bahasa
tanpa sengaja memperbaiki bug itu, menurut pendapat saya, luar biasa
dan layak dihormati. TS memiliki kompleksitas desain yang sama dengan C++ di sini, tetapi
sistem tipe ekspresif membuat situasi menjadi lebih buruk.

Pada Jumat, Jun 19, 2020, 00:47 Bennett Piater [email protected] menulis:

Saya tentu saja tidak mendekati kalibernya, tetapi saya dengan hormat tidak setuju
dengan @ahejlsberg https://github.com/ahejlsberg .
Dalam pengalaman saya, sebagian besar kompleksitas TypeScript berasal darifakta bahwa banyak fitur (menarik dan berguna tentunya) adalahkasus khusus sebagai konsep mereka sendiri.

Kompleksitas ini tidak secara inheren merupakan fungsi dari jumlah fitur
meskipun!
Sebaliknya, bahasa dapat dirancang lebih besar, lebih menyeluruh
konsep dari mana kasus-kasus khusus ini kemudian dapat disimpulkan secara sepele, atau
diimplementasikan di perpustakaan std (tipe).

Konsep seperti itu yang paling umum tentu saja adalah mengimplementasikan sepenuhnya
tipe dependen, dari mana segala sesuatu yang lain kemudian dapat diturunkan, tetapi
pergi sejauh itu tidak perlu:
Seperti C++ dan, pada tingkat lebih rendah, Rust telah menunjukkan, beberapa skala besar,
konsep yang konsisten memberi Anda banyak fitur gratis.
Ini mirip dengan apa yang telah dilakukan OCaml dan Haskell (dan saya berasumsi F#?)
tingkat nilai, hanya pada tingkat jenis.

Pemrograman tingkat tipe tidak perlu ditakuti selama itu
dirancang ke dalam bahasa alih-alih ditempelkan untuk memberikan
fitur.
Fasilitas di C++ 14/17 sangat intuitif kecuali untuk sintaksnya,
yang murni karena beban sejarah.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646543896 ,
atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/AAWYQIMWYLGGCWPTDBZJR4TRXMX4RANCNFSM4BTBQ7DQ
.

@polkovnikov-ph Saya senang kami menyetujui masalah yang dihadapi :)

Adapun solusinya, saya pikir masih layak dipertimbangkan untuk secara progresif bergerak menuju sistem tipe yang dirancang lebih hati-hati. Bagaimanapun juga, versi utama adalah suatu hal, dan alternatifnya adalah berakhir di cluster * * yaitu C++ 20 - upaya yang mengagumkan untuk menambahkan fitur yang dirancang dengan lebih baik di atas 2 lapisan upaya sebelumnya yang tidak dapat dihapus, dalam a sintaks yang sudah tidak dapat diuraikan secara deterministik.

Semua ini di luar topik untuk utas ini dan sedang dibahas di sini . Jadi saya akan mencoba jujur:

Butuh beberapa dekade bagi akademisi untuk menemukan pendekatan yang tepat untuk subtipe: sistem tipe mlsub dibuat hanya 6 tahun yang lalu, jauh setelah TypeScript pertama kali dirilis. Bisa jadi fondasi itu untuk kelas, antarmuka, gabungan, dan tipe persimpangan dengan konsep menyeluruh.

Tetapi juga ingat ada tipe kondisional. Saya tidak mengetahui adanya makalah yang memberi mereka semantik formal, atau menggambarkan sistem tipe minimal dengan tipe bersyarat dengan bukti kemajuan/pelestarian. Saya percaya itu mungkin ada hubungannya dengan para ilmuwan yang masih malu untuk mencetak upaya mereka yang gagal. Jika proposal Anda mengasumsikan versi utama yang tidak kompatibel itu akan dibuat pada tahun 2040-an, ketika akademisi merasa nyaman dengan tipe bersyarat, saya bisa setuju.

Kalau tidak, "sistem tipe yang dirancang dengan hati-hati" harus menghapus tipe bersyarat dari bahasa, dan saya tidak berpikir ada orang yang dapat mengubah 60% dari PastiTyped untuk menggunakan alternatif apa pun yang dipilih untuk menggantikannya. (Dan kemudian lakukan beberapa kali lagi, karena itu bukan satu-satunya masalah.)

Saya khawatir satu-satunya solusi yang layak adalah membuat bahasa pemrograman terpisah yang entah bagaimana akan menyerupai TS, dan entah bagaimana (tidak hanya dengan menjadi lebih menyenangkan untuk menulis kode) memikat pengembang untuk menggunakannya. Ryan cukup vokal merekomendasikan pendekatan ini untuk perbaikan TS sebelumnya.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

kyasbal-1994 picture kyasbal-1994  ·  3Komentar

zhuravlikjb picture zhuravlikjb  ·  3Komentar

weswigham picture weswigham  ·  3Komentar

siddjain picture siddjain  ·  3Komentar

blendsdk picture blendsdk  ·  3Komentar