Typescript: Izinkan kelas menjadi parametrik di kelas parametrik lainnya

Dibuat pada 19 Nov 2014  ·  140Komentar  ·  Sumber: microsoft/TypeScript

Ini adalah proposal untuk mengizinkan obat generik sebagai parameter tipe. Saat ini dimungkinkan untuk menulis contoh monad tertentu, tetapi untuk menulis antarmuka yang dipenuhi oleh semua monad, saya mengusulkan untuk menulis

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

Demikian pula, dimungkinkan untuk menulis contoh spesifik dari fungsi kartesian, tetapi untuk menulis antarmuka yang dipenuhi oleh semua fungsi kartesian, saya mengusulkan untuk menulis

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

Parameter tipe parametrik dapat mengambil sejumlah argumen:

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

Artinya, ketika parameter tipe diikuti oleh tilde dan natural arity, parameter tipe harus diizinkan untuk digunakan sebagai tipe generik dengan arity yang diberikan di sisa deklarasi.

Seperti halnya sekarang, saat mengimplementasikan antarmuka seperti itu, parameter tipe generik harus diisi:

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

Selain secara langsung mengizinkan komposisi tipe generik dalam argumen, saya mengusulkan bahwa typedef juga mendukung pendefinisian generik dengan cara ini (lihat masalah 308 ):

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

Arities dari definisi dan alias harus cocok agar typedef valid.

Suggestion help wanted

Komentar yang paling membantu

dengan pola pikir HKT dapat diubah, kebiasaan rusak, generasi yang hilang dihidupkan kembali, itu akan menjadi hal terbesar sejak generik dan eksplisit nol dan tidak terdefinisi, itu dapat mengubah segalanya

tolong anggap itu sebagai fitur besar berikutnya, berhenti mendengarkan orang-orang yang terus meminta Anda untuk kuda yang lebih baik, beri mereka f * g ferrari

Semua 140 komentar

Bukan untuk membuat asumsi gegabah, tapi saya yakin Anda salah mengetik. Semua tipe parameter membutuhkan nama parameter, jadi Anda mungkin bermaksud untuk mengetik

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

sedangkan sekarang map adalah fungsi yang mengambil mapper dari tipe any (di mana nama parameter Anda adalah A ) menjadi B .

Coba gunakan bendera --noImplicitAny untuk hasil yang lebih baik.

Terima kasih, dikoreksi.

Saya telah memperbarui komentar saya menjadi proposal.

: +1: tipe yang lebih tinggi akan menjadi bonus besar untuk konstruksi pemrograman fungsional, namun sebelumnya saya lebih suka memiliki dukungan yang benar untuk fungsi orde tinggi dan generik: p

Kuasi disetujui.

Kami sangat menyukai ide ini, tetapi membutuhkan implementasi yang berfungsi untuk mencoba memahami semua implikasi dan kasus edge potensial. Memiliki sampel PR yang setidaknya menangani 80% kasus penggunaan ini akan menjadi langkah selanjutnya yang sangat membantu.

Apa pendapat orang tentang sintaks tilde? Alternatif untuk T~2 adalah sesuatu seperti

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

yang memungkinkan komposisi generik langsung daripada membutuhkan alias tipe:

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

Aneh rasanya memiliki arity eksplisit karena kita tidak benar-benar melakukannya di tempat lain, jadi

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

sedikit lebih jelas, meskipun, saya tahu bahasa lain menggunakan * dalam konteks yang serupa, bukan ~ :

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

Meskipun mengambil titik itu secara ekstrem, Anda mungkin mendapatkan:

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

Saya pikir T<~,~> juga lebih jelas dari T~2 . Saya akan mengubah proposal di atas. Saya tidak peduli apakah kita menggunakan ~ atau * ; itu tidak bisa menjadi pengenal JS, jadi kita tidak bisa menggunakan, katakanlah, _ . Saya tidak melihat manfaat apa yang diberikan oleh notasi => ; semua obat generik mengambil beberapa jenis masukan dan mengembalikan jenis keluaran tunggal.

Sintaks yang lebih ringan akan menghilangkan arity dari generik sepenuhnya; parser akan mengetahuinya dari penggunaan pertama dan menampilkan kesalahan jika sisanya tidak konsisten dengannya.

Saya akan dengan senang hati mulai bekerja menerapkan fitur ini. Apa forum yang direkomendasikan untuk mengganggu developer tentang detail implementasi transpiler?

Anda dapat mencatat banyak masalah baru untuk pertanyaan yang lebih besar dengan lebih banyak sampel kode yang terlibat, atau membuat masalah yang berjalan lama dengan serangkaian pertanyaan saat Anda melanjutkan. Atau Anda dapat bergabung dengan ruang obrolan di sini https://gitter.im/Microsoft/TypeScript dan kita dapat berbicara di sana.

@metaweta ada berita? Jika Anda membutuhkan bantuan / diskusi, saya akan senang untuk melakukan brainstorming tentang masalah ini. Saya sangat menginginkan fitur ini.

Tidak, hal-hal di tempat kerja mengambil alih waktu luang yang harus saya kerjakan.

benjolan: apakah ada kemungkinan untuk melihat fitur ini dipertimbangkan?

https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -96854288 masih dalam keadaan saat ini. Saya tidak melihat apa pun di sini yang akan membuat kami mengubah prioritas fitur.

Menurut saya seperti ini berguna dalam lebih banyak situasi daripada hanya mengimpor abstraksi teori kategori. Misalnya, akan berguna untuk dapat menulis pabrik modul yang menggunakan Promise implementasi (konstruktor) sebagai argumen, misalnya Database dengan implementasi promise yang dapat dimasukkan:

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

: +1:

dengan pola pikir HKT dapat diubah, kebiasaan rusak, generasi yang hilang dihidupkan kembali, itu akan menjadi hal terbesar sejak generik dan eksplisit nol dan tidak terdefinisi, itu dapat mengubah segalanya

tolong anggap itu sebagai fitur besar berikutnya, berhenti mendengarkan orang-orang yang terus meminta Anda untuk kuda yang lebih baik, beri mereka f * g ferrari

Yup, Langsung ke 15 menit pertama ini setelah mencoba menambahkan jenis ke basis kode JS yang ada. Saya tidak akan beralih ke TS sampai saya melihatnya.

Bisakah saya membantu?

Saya bertanya-tanya bagaimana ini akan berhubungan dengan # 7848? Mereka sangat mirip, meskipun tentang segi lain dari jenis tingkat tinggi.

@boris-marinov balasan Ryan Cavanaugh mengatakan Anda dapat:

Memiliki sampel PR yang setidaknya menangani 80% kasus penggunaan ini akan menjadi langkah selanjutnya yang sangat membantu.

Sekarang saya punya waktu untuk mengimplementasikan PR sederhana Harapan untuk mendapatkan beberapa petunjuk dari pengembang inti, tetapi sejauh ini tidak ada pertanyaan - semuanya terlihat bagus dan dapat dimengerti. Akan melacak kemajuannya di sini.

@Artazor Apakah Anda ingin melihat cracking # 7848 juga? Itu menangani sisi lain dari masalah ini, yang melibatkan obat generik, dan IMHO ini akan terasa tidak lengkap tanpanya (parameter generik benar-benar akan menyederhanakan banyak kode tingkat tipe).

Saya pikir proposal ini benar-benar luar biasa. Memiliki jenis jenis yang lebih tinggi di TypeScript akan membawanya ke tingkat yang baru di mana kami dapat menggambarkan abstraksi yang lebih kuat daripada yang mungkin saat ini.

Namun, tidakkah ada yang salah dengan contoh yang diberikan di OP? A di baris

class ArrayMonad<A> implements Monad<Array> {

tidak digunakan dalam salah satu metode, karena mereka semua memiliki A generiknya sendiri.

Juga, jika mengimplementasikan functor dengan map sebagai metode yang menggunakan this akan terlihat seperti apa? Mungkin seperti ini?

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

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

@paldepind Lihat # 7848. Diskusi itu adalah tentang kasus penggunaan tertentu, meskipun IMHO ini dan itu benar-benar perlu digabung menjadi satu PR.

Kapan barang ini akan mendarat? Sepertinya itu penting.

Juga apakah itu akan memungkinkan seperti:

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

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

?

@whitecolor Saya pikir ada ikan yang lebih besar untuk digoreng saat ini, yang mendapat prioritas lebih tinggi:

  1. TypeScript 2.0 RC dirilis hanya kurang dari 2 minggu yang lalu. Itu sendiri akan memakan banyak waktu.
  2. bind , call , dan apply , fungsi JS asli, tidak diketik. Ini sebenarnya tergantung pada proposal generik variadic . Object.assign juga membutuhkan perbaikan serupa, tetapi variadic generics saja tidak akan menyelesaikannya.
  3. Fungsi-fungsi seperti metode Lodash _.pluck , model Backbone get dan set , dll. Saat ini tidak diketik , dan memperbaiki ini pada dasarnya membuat Backbone dapat digunakan dengan TypeScript dengan cara yang jauh lebih aman. Ini juga mungkin memiliki implikasi untuk React di masa depan .

Bukannya saya tidak menginginkan fitur ini (saya akan _love_ untuk fitur seperti itu), saya hanya tidak melihatnya kemungkinan akan segera hadir.

@isiah
Terima kasih atas penjelasannya. Ya, item ke-3 dalam daftar sangat penting, menunggu https://github.com/Microsoft/TypeScript/issues/1295 juga.

Tapi saya berharap untuk masalah saat ini mungkin di 2.1dev entah bagaimana.

Saya setuju. Semoga bisa masuk.

(Polimorfisme peringkat 2, yang diinginkan oleh masalah ini, juga merupakan kebutuhan
Pengguna Fantasy Land, untuk mengetikkan berbagai ADT dengan benar dalam spesifikasi itu.
Ramda adalah contoh bagus dari perpustakaan yang sangat membutuhkan ini.)

Pada hari Selasa, 6 Sep 2016, 11:00 Alex [email protected] menulis:

@isiahmeadows https://github.com/isiahmeadows
Terima kasih atas penjelasannya. Ya, item ke-3 dalam daftar sangat penting,
menunggu # 1295 https://github.com/Microsoft/TypeScript/issues/1295
terlalu.

Tapi saya berharap untuk masalah saat ini mungkin di 2.1dev entah bagaimana.

-
Anda menerima ini karena Anda disebutkan.

Balas email ini secara langsung, lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -244978475,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AERrBMvxBALBe0aaLOp03vEvEyokvxpyks5qnX_8gaJpZM4C99VY
.

Tampaknya fitur ini akan banyak membantu kita dalam mendefinisikan bentuk reaksi. Misalnya, Anda memiliki struct:

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

Saya memiliki penangan, yang didefinisikan sebagai:

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

handler ini diteruskan sebagai props untuk komponen React. Saya juga memiliki fungsi, yang mengambil struct dan mengembalikan struct yang sama, tetapi dengan Penangan, bukan nilai:

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

Sedangkan untuk saat ini saya tidak bisa mengetikkan fungsi itu dengan baik, karena TS tidak bisa menghasilkan tipe berdasarkan struktur tipe lain.

Sapi saya bisa mengetik makeForm dengan HKT?

Hm, menarik.

Mungkin hal seperti ini mungkin terjadi:

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

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

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

@ boris-marinov Hal yang paling menarik adalah baris ini:

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

mungkin perlu disebutkan bahwa HKT bisa menjadi jawaban untuk apa yang disebut tipe parsial (https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155):

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

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

Tidak terlalu. {x: number?} tidak dapat dialihkan ke {x?: number} , karena satu
dijamin ada, sedangkan yang lainnya tidak.

Pada Selasa, 11 Okt 2016, 09:16 Aleksey Bykov [email protected] menulis:

mungkin perlu disebutkan bahwa HKT bisa menjadi jawaban atas apa yang disebut
tipe parsial (# 4889 (komentar)
https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155
):

ketik MyDataProto satu: K;
lainnya: K;
yetAnother: K;
} tipe Identical = a; type Opsional = a ?;
= {get (): a;

;
;
;

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -252913109,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AERrBNFYFfiW01MT99xv7UE2skQ3qiPMks5qy4wRgaJpZM4C99VY
.

@isiahmeadows Anda benar, saat ini tidak ada cara / sintaks untuk membuat properti benar-benar opsional hanya berdasarkan jenisnya, dan itu memalukan

Satu lagi: alangkah baiknya jika properti dapat dibuat readonly . Sepertinya beberapa jenis fitur makro diperlukan.

Hanya membuang ini di luar sana ... Saya lebih suka sintaks * sintaks ~ . Sesuatu tentang ~ sepertinya sangat jauh dari perspektif tata letak keyboard. Juga, saya tidak yakin mengapa, tapi saya pikir * tampaknya sedikit lebih mudah dibaca / dibedakan dengan semua tanda kurung sudut yang ada dalam campuran. Belum lagi, orang yang akrab dengan bahasa lain seperti Haskell mungkin langsung mengaitkan sintaks tersebut dengan HKT. Sepertinya sedikit lebih alami.

Saya harus setuju dengan sintaks * . Pertama, ini lebih bisa dibedakan,
dan kedua, itu lebih baik mewakili jenis "semua jenis bekerja".


Isiah Meadows
[email protected]

Pada hari Minggu, 6 November 2016 pukul 00.10, Landon Poch [email protected]
menulis:

Hanya membuang ini di luar sana ... Saya lebih suka sintaks * daripada ~ sintaks.
Sesuatu tentang ~ sepertinya sangat jauh dari tata letak keyboard
perspektif. Selain itu, saya tidak yakin mengapa, tetapi menurut saya * tampaknya sedikit lebih
dapat dibaca / dibedakan dengan semua tanda kurung sudut yang ada di campuran.
Belum lagi, orang yang akrab dengan bahasa lain seperti Haskell mungkin
segera kaitkan sintaksnya dengan HKT. Sepertinya sedikit lebih alami.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -258659277,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AERrBHQ4SYeIiptB8lhxEAJGOYaxwCkiks5q7VMvgaJpZM4C99VY
.

Pencapaian: community ? Bagaimana kondisi masalah / fitur saat ini?

@whitecolor statusnya adalah DIY (lakukan sendiri)

Masalah memiliki label Accepting PRs . ini berarti permintaan pull untuk mengimplementasikan fitur ini disambut baik. Lihat https://github.com/Microsoft/TypeScript/wiki/FAQ#what -do-the-labels-on-these-issues-mean untuk detail selengkapnya.

Lihat juga https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -96854288

Oke, saya melihat labelnya, hanya ragu jika tim non-TS mampu melakukannya.

Sekarang saya punya waktu untuk mengimplementasikan PR sederhana Harapan untuk mendapatkan beberapa petunjuk dari pengembang inti, tetapi sejauh ini tidak ada pertanyaan - semuanya terlihat bagus dan dapat dimengerti. Akan melacak kemajuannya di sini.

@Artazor Apakah Anda beruntung dengan ini?

@raveclassic - ternyata lebih sulit dari yang terlihat, namun saya masih berharap untuk terus maju. Secara sintaksis sudah jelas, tetapi aturan / fase pemeriksaan ketik tidak sejelas yang saya inginkan -)

Mari kita coba menghidupkan kembali aktivitas saya -)

Hanya melacak kemajuan, dan jalur pengembangan ide. Saya telah mempertimbangkan tiga opsi bagaimana menerapkan fitur ini.

Saya telah merencanakan untuk memperkaya TypeParameterDeclaration dengan properti opsional higherShape

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

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

dan telah mempertimbangkan tiga opsi bagaimana HigherShape dapat diterapkan

1. Arity Sederhana untuk Domain

type HigherShape = number

itu sesuai dengan penggunaan berikut:

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

dalam kasus yang paling sederhana ini, sepertinya tipe number sudah cukup. Namun demikian, kita harus dapat menentukan HigherShape aktual untuk setiap tipe yang diberikan untuk memastikan bahwa kami dapat menggunakannya sebagai argumen tipe untuk persyaratan bentuk tertentu. Dan di sini kita menghadapi masalah: bentuk yang lebih tinggi dari kelas Demo itu sendiri tidak dapat diekspresikan sebagai angka. Jika ya, maka harus direpresentasikan sebagai 2 - karena memiliki dua jenis parameter,
dan dimungkinkan untuk menulis

var x: Demo<Array, Demo>

dan kemudian berjuang dengan masalah pemeriksaan tipe yang ditangguhkan dengan properti .both . Jadi tipe number tidak cukup (saya percaya);

sebenarnya tipe Demo memiliki bentuk urutan tinggi berikut:

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

2. Domain dan Co-Domain terstruktur sepenuhnya

Kemudian saya telah menyelidiki kebalikannya, representasi paling lengkap dari bentuk-bentuk yang lebih tinggi, yang memungkinkan representasi bentuk-bentuk seperti yang disebutkan di atas, dan bahkan lebih buruk:

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

Struktur data untuk ini sangat mudah, tetapi tidak berinteraksi dengan baik dengan sistem tipe TypeScript. Jika kita mengizinkan tipe tingkat tinggi seperti itu maka kita tidak akan pernah tahu apakah * berarti jenis dasar, yang dapat digunakan untuk pengetikan nilai. Selain itu, saya bahkan tidak berhasil menemukan sintaks yang tepat untuk mengekspresikan batasan urutan yang lebih tinggi yang mengerikan.

3. Domain Terstruktur / Co-Domain Sederhana Terstruktur

Ide utama - ekspresi tipe (bahkan dengan argumen tipe aktual) selalu menghasilkan tipe dasar - yang dapat digunakan untuk mengetik variabel. Di sisi lain, setiap parameter tipe dapat memiliki parameter tipe detailnya sendiri dalam format yang sama yang digunakan di tempat lain.

Ini adalah keputusan terakhir saya yang akan saya coba dukung.

type HigherShape = NodeArray<TypeParameterDeclaration>;

contoh:

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

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

Di sini saya ingin mencatat, bahwa M adalah lokal untuk T1<M extends A> extends Z<M> dan berada dalam cakupan visibilitas yang lebih dalam daripada T1. Jadi M tidak tersedia di tubuh SomeClass .
Dan * berarti hanya pengenal baru (tipe anonim) yang tidak pernah bentrok dengan apa pun (dan dapat diterapkan di tahap selanjutnya)


Jadi tanda tangan terakhir dari TypeParameterDeclaration

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

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

Mau dengar opini @DanielRosenwasser , @ aleksey-bykov, @isiahmeadows dan lain-lain -)

Kedengarannya oke bagi saya, tetapi saya tahu sangat sedikit tentang struktur internal basis kode TypeScript.

Ingin menambahkan suara saya ke paduan suara meminta ini dan untuk mendukung Anda, Artazor! :)

Fitur ini akan berguna bagi saya dalam implementasi membuat Redux type-safe.

@michaeltontchev Masalah apa yang Anda hadapi dalam membuat Redux aman untuk mengetik?

Jika Anda tertarik, saya baru-baru ini menerbitkan https://github.com/bcherny/tdux dan https://github.com/bcherny/typed-rx-emitter , yang mengembangkan ide-ide dari Redux dan EventEmitter.

Sekarang terlihat, perlu me -rebase ke 13487 dengan parameter generik default. Dalam kasus lain, kami akan konflik secara luas.

@bcherny - terima kasih untuk

Saya sedang mempelajari cara membuat jenis ComboReducers aman dengan memastikan ia memiliki peredam jenis yang tepat untuk setiap properti negara bagian (tanpa tambahan). Saya berhasil melakukannya dalam kasus khusus ini tanpa obat generik bersarang, tetapi solusi bersarang akan lebih baik. Saya memiliki yang berikut ini:

import { combineReducers, Reducer } from 'redux';

interface IState {
    // my global state definition
}

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

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

let combinedReducers = combineReducers<IState>(statePropertyToReducerMap);

Pada dasarnya, tipe yang saya perkenalkan di atas menjamin bahwa pemetaan peredam yang Anda lewati untuk menggabungkanReduser mencakup setiap properti negara bagian Anda dan memiliki jenis pengembalian yang tepat. Saya tidak dapat menemukan solusi seperti itu saat mencari online - menurut saya ini tidak dapat dilakukan tanpa fitur keyof, yang keluar hanya dua bulan yang lalu :)

Saya berharap fitur keyof akan berguna untuk ImmutableJs juga untuk membuat setter dan getter type-safe, meskipun Anda mungkin masih memerlukan beberapa perkakas tambahan di sekitarnya.

Sunting: untuk mengklarifikasi, generik bersarang akan memungkinkan saya tidak perlu melakukan hardcode jenis Reducer dalam tipe StatePropertyNameAndTypeAwareReducer, tetapi meneruskannya sebagai generik, tetapi harus menjadi generik bersarang, yang tidak mungkin dilakukan di saat ini.

Edit2: membuat masalah untuk Redux di sini: https://github.com/reactjs/redux/issues/2238 Sepertinya mereka tidak melakukan banyak TypeScript, jadi mereka mencari orang TypeScript yang tahu Redux untuk dipertimbangkan.

Bagaimana jalannya?

Mungkin pertanyaan yang naif, tetapi mengapa ~ atau * bukan parameter umum biasa? Apakah itu untuk menunjukkan bahwa itu tidak digunakan? Yaitu. kenapa tidak:

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

Atau:

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

Atau bahkan:

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

@bcherny Saya yakin ini menyebabkan ambiguitas dalam sintaks, karena Functor<A<T>> sebelumnya berarti " A dari T ", di mana T adalah sejenis bahasa lokal cakupan. Ini tidak mungkin, tetapi sintaks ini mungkin juga menjadi perubahan yang merusak untuk beberapa basis kode, karena alasan yang sama.

@asaeedu begitu. Sintaks baru berarti "mengikat T malas", daripada "mengikat T secara ketat dalam lingkup saat ini".

Yang mengatakan, menurut saya proposal @DanielRosenwasser untuk T: * => * paling masuk akal di sini, karena ada "seni sebelumnya" untuk itu.

Di Haskell, operator -> sebenarnya adalah tipe parametrized (mungkin lebih mudah untuk memvisualisasikan Func<TArg, TRet> ). Tipe konstruktor -> menerima dua tipe sembarang T dan U dan menghasilkan tipe konstruktor nilai (yaitu fungsi) yang memetakan nilai tipe T ke nilai tipe U .

Hal yang menarik adalah ia juga merupakan konstruktor yang baik! Konstruktor jenis -> menerima dua jenis sembarang T* dan U* (tanda bintang hanya untuk pembedaan visual), dan menghasilkan jenis konstruktor yang memetakan jenis jenis T* ke jenis jenis U* .

Anda mungkin melihat pola pada saat ini. Sintaks dan semantik yang digunakan untuk mendefinisikan dan merujuk ke tipe hanya digunakan kembali untuk mendefinisikan dan merujuk ke jenis. Faktanya, itu bahkan tidak digunakan kembali, itu hanya secara implisit mendefinisikan hal-hal di dua alam semesta yang berbeda sekaligus. (fakta bahwa itu isomorfik sebenarnya berarti bahwa ini mampu mendefinisikan hal-hal dalam level tak terbatas, nilai -> jenis -> jenis -> macam -> ..., kecuali untuk * malang, tapi itu topik untuk waktu yang berbeda)

Faktanya, pola ini sangat masuk akal sehingga beberapa orang menerapkan ekstensi GHCi yang banyak digunakan yang menggeneralisasikannya ke semua tipe konstruktor, bukan hanya -> . Ekstensi ini disebut "jenis data", dan begitulah cara Haskell mendapatkan daftar heterogennya ( [] adalah jenis daftar nilai, dan jenis daftar jenis), tupel heterogen, "panjang cerdas "vektor, dan banyak fitur lainnya.

Mungkin kita belum ingin mencapai DataKinds dulu, jadi kita akan tetap menggunakan konstruktor jenis * dan -> , tetapi jika kita mengikuti sintaks yang diusulkan Daniel , atau lebih umum membuat definisi jenis isomorfik untuk definisi jenis, kami membuka diri untuk memanfaatkan perkembangan masa depan di bidang ini.

Mengikuti posting bertele-tele saya sebelumnya, saya ingin merekomendasikan agar kita menggunakan any daripada * ; ini mewakili jenis setiap nilai dan jenis setiap jenis. Jika sintaksnya tampak membingungkan, kita dapat mengambil halaman dari buku Haskell dan menggunakan awalan ' untuk membedakan jenis dan tipe.

Contoh OP kemudian akan ditulis seperti ini:

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

Nitpick: Saya menemukan any membingungkan secara umum dalam arti ia melakukan dua hal yang berbeda.
Ini adalah tipe super dari semua yang lain, seperti tidak pernah merupakan sub tipe dari semua yang lain, jadi jika suatu fungsi meminta parameter any , Anda dapat memasukkan apa pun. Sejauh ini bagus.
Bagian yang lucu adalah ketika suatu fungsi meminta sesuatu yang spesifik, dan Anda memberikan any . Jenis pemeriksaan ini, sementara setiap jenis lain yang lebih luas dari yang diminta akan membuatnya menjadi kesalahan.
Tapi ya, terserah.

Di catatan lain ' akan membingungkan karena ini juga digunakan dalam literal string.

@Artazor Ada berita tentang ini? Terakhir kali Anda menyebutkan, Anda perlu melakukan rebase pada parameter generik default. Dan menurut saya, Anda adalah satu-satunya yang cukup dekat dengan POC yang berfungsi.

Penting juga untuk memikirkan bagaimana ini berinteraksi dengan subtipe. Menggunakan * tidak cukup; dalam bahasa yang menggunakan polimorfisme ad-hoc daripada polimorfisme terbatas, Anda memiliki jenis kendala untuk membatasi jenis argumen yang dapat diterima. Sebagai contoh, jenis Monad T sebenarnya adalah Constraint , bukan * .

Dalam TypeScript kita menggunakan subtipe struktural, jadi jenis kita perlu mencerminkan hubungan subtipe antar jenis. Makalah Scala tentang hal ini mungkin menghasilkan beberapa ide bagus tentang bagaimana merepresentasikan varians dan hubungan subtipe dalam sistem yang baik: " Menuju Hak Setara untuk Jenis yang Lebih Baik ".

Ada kemajuan dalam hal ini?

Pendekatan alternatif oleh @gcanti https://medium.com/@gcanti/higher -kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

Masalah dengan pendekatan yang diambil oleh fp-ts adalah hal itu membuat Anda mengimplementasikan ulang library yang terbukti. Bagi saya, gagasan ketikan adalah untuk dapat mengetik dengan benar apa yang saat ini dianggap sebagai praktik terbaik di JavaScript, bukan untuk memaksa Anda menerapkannya kembali dengan cara tsb.

Ada banyak contoh di sini yang menunjukkan bahwa HKT diperlukan untuk mendeskripsikan dengan benar kontrak yang saat ini kami gunakan di js libs, baik dalam bentuk fantasy land, ramda atau react.

Akan sangat menyenangkan melihat ini diterapkan :)

~ Apakah ada yang mau / mampu mengerjakan ini dibayar? Jangan ragu untuk menghubungi saya untuk berdiskusi. Atau siapa pun dapat melatih seseorang yang mungkin kami temukan untuk mengerjakan ini, beri tahu saya juga. ~ [EDIT: Saya [mungkin memutuskan] (https://github.com/keean/zenscript/issues/35#issuecomment -357567767) untuk meninggalkan ekosistem ini dan komentar saya selanjutnya di utas ini membuat saya menyadari bahwa ini mungkin akan menjadi usaha yang sangat besar]

Pendekatan alternatif oleh @gcanti https://medium.com/@gcanti/higher -kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

Saya tidak repot-repot untuk sepenuhnya melakukannya, karena saya mengamati hasil map masih secara eksplisit menentukan jenis kontainer Option dan dengan demikian tidak sepenuhnya generik dengan cara yang lebih baik jenis (HKT) dapat menyediakan:

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

Seperti yang dicatat oleh @ion pada 26 Agustus 2016 , HKT diperlukan untuk membuat generik fungsi apa pun yang membutuhkan pabrik dan di mana tipe parametrized kontainer harus menjadi generik itu sendiri. Kami telah menjelajahi ini dalam diskusi kami tentang desain bahasa pemrograman.

NB Jika Anda penasaran, fitur ini menjadi faktor signifikan dalam analisis saya (termasuk @keean ) tentang lanskap bahasa pemrograman .

@ shelby3 FWIW Option 's map (dalam Option.ts ) tidak umum karena mewakili instance sementara Functor map (dalam Functor.ts ) bersifat umum karena mewakili kelas tipe . Kemudian Anda dapat menentukan fungsi lift generik yang dapat beroperasi dengan instance functor apa pun.

Akan sangat menyenangkan melihat ini diterapkan :)

Saya sangat setuju :)

@ shelby3 : untuk mendapatkan fitur seperti ini digabungkan, taruhan terbaik Anda mungkin mendapatkannya
mereka untuk memprioritaskannya pada peta jalan TS; Saya memiliki beberapa PR yang terutama didapat
umpan balik / penggabungan baik ketika perbaikan kecil atau jika mereka sudah mencari
ke dalam mereka. Saya tidak ingin menjadi negatif tetapi itu pertimbangan jika Anda
akan menginvestasikan sumber daya untuk ini.

Pada 8 Jan 2018 16.05, "shelby3" [email protected] menulis:

Adakah yang mau / mampu mengerjakan ini dibayar? Jangan ragu untuk menghubungi
saya [email protected] untuk berdiskusi. Atau siapa pun bisa melatih seseorang
kami mungkin menemukan cara untuk mengerjakan ini, harap beri tahu saya juga.

Pendekatan alternatif oleh @gcanti https://github.com/gcanti
https://medium.com/@gcanti/higher -kinded-types-in-
ketikan-statis-dan-fantasi-tanah-d41c361d0dbe

Saya tidak repot-repot sepenuhnya melakukannya, karena saya mengamati peta yang dihasilkan
masih secara eksplisit menentukan jenis penampung Opsi dan karenanya tidak sepenuhnya
generik dengan cara yang lebih baik jenisnya (HKT) dapat menyediakan:

peta fungsi (f: (a: A) => B, fa: HKTOption ): Option { return (fa sebagai Option ) .map (f) }

HKT diperlukan untuk membuat fungsi generik apa pun yang membutuhkan pabrik dan
di mana tipe tipe kontainer parametrized akan menjadi generik itu sendiri. Kita memiliki
menjelajahi https://github.com/keean/zenscript/issues/10 ini di kami
diskusi tentang desain bahasa pemrograman.

PS Jika Anda penasaran, faktor fitur ini secara signifikan menjadi milik saya
(termasuk @keean https://github.com/keean 's) analisis
lanskap bahasa pemrograman
https://github.com/keean/zenscript/issues/35#issuecomment-355850515 . saya
menyadari pandangan kami tidak sepenuhnya berkorelasi dengan prioritas Typecript
dengan tujuan utama menjadi superset dari Javascript / ECMAScript dan support
ekosistem itu.

-
Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-355990644 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AC6uxYOZ0a8G86rUjxvDaO5qIWiq55-Fks5tIi7GgaJpZM4C99VY
.

@gcaniti mohon maaf atas kebisingannya dan terima kasih atas penjelasan tambahannya. Saya seharusnya belajar lebih banyak sebelum berkomentar. Tentu saja, ini adalah kesalahan saya dalam konseptualisasi karena (saya sudah tahu) sebuah functor memerlukan implementasi contoh.

Afaics, "hack" pintar Anda memungkinkan merujuk ke pabrik secara umum (mis. lift ), tetapi memerlukan pelat tambahan Augmentasi Modul untuk (memperbarui dan) mengkhususkan setiap pengetikan pabrik generik untuk jenis khusus dari functor , misalnya Option . Bukankah boilerplate itu diperlukan untuk setiap penggunaan umum dari pabrik generik, misalnya contoh sort generik @keean dan saya diskusikan? Mungkinkah ada koper lain yang bisa ditemukan juga?

Apakah Kotlin meniru ide Anda atau sebaliknya? (beberapa kritik tambahan di tautan itu tetapi saya tidak tahu apakah itu berlaku untuk kasus Ketikan)

Saya tidak ingin menjadi negatif tetapi ini adalah pertimbangan jika Anda akan menginvestasikan sumber daya untuk ini.

Ya, pikiran itu juga terpikir olehku. Terima kasih telah mengartikulasikannya. Saya menduga salah satu pertimbangannya adalah dampak secara umum pada sistem tipe dan kasus sudut apa pun yang mungkin ditimbulkannya, seperti yang ditunjukkan oleh @masaeedu. Mungkin akan ada penolakan kecuali hal ini dipikirkan dan didemonstrasikan dengan sangat cermat.

Catatan Saya juga melihat ke Ceylon untuk lebih memastikan apa tingkat investasi mereka ke dalam target kompilasi EMCAScript. (Saya perlu belajar lebih banyak).

Saya juga baru saja digigit oleh batasan ini. Saya ingin I dalam contoh berikut ini disimpulkan secara otomatis:


interface IdType<T> {
  id: T;
}

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

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

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

Sejauh yang saya tahu, ini membutuhkan tipe yang lebih tinggi atau menentukan parameter generik duplikat (misalnya doStuff<User, number>() ) yang tampaknya berlebihan.

Saya juga baru-baru ini dikejutkan oleh batasan ini.

Saya telah bekerja di perpustakaan untuk janji . Ini menyediakan berbagai fungsi utilitas untuk bekerja dengannya.

Fitur utama library ini adalah mengembalikan jenis janji yang sama dengan yang Anda masukkan ke dalamnya. Jadi jika Anda menggunakan janji Bluebird dan memanggil salah satu fungsinya, itu akan mengembalikan janji Bluebird dengan semua fungsionalitas tambahan yang mereka sediakan.

Saya ingin mengenkode ini dalam sistem tipe, tetapi saya segera menyadari bahwa ini memerlukan bekerja dengan tipe P dari jenis * -> * sehingga P<T> extends Promise<T> .

Berikut adalah contoh dari salah satu fungsi tersebut:

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

Dalam situasi di atas, saya dapat menghindari kebutuhan jenis yang lebih tinggi dengan menggunakan jenis this agak hacky.

Namun, kasus berikut tidak dapat diselesaikan:

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

Karena tidak ada peretasan yang memungkinkan saya melakukan this<T[]> atau sesuatu.

Catat permintaan maaf kecil saya di dokumentasi.

Punya skenario lain di mana saya yakin fitur ini akan berguna (dengan asumsi saya telah menafsirkan proposal dengan benar), seperti yang ditunjukkan oleh referensi di atas.

Dalam paket yang dimaksud, perlu untuk memiliki kelas atau fungsi generik tanpa tipe yang digunakan sebagai tipe, karena tipe generik biasanya dibuat oleh pengguna.

Menerapkan proposal ke skenario saya, saya yakin akan terlihat seperti:

import { Component, FunctionalComponent } from 'preact';

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

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

Mengingat bahwa tidak ada cara untuk mereferensikan tipe generik yang andal dalam implementasi saya, saya yakin saya telah melewatkan sesuatu.

@ Silic0nS0ldier Sebenarnya kasus itu dapat diselesaikan sekarang. Anda menggunakan tipe konstruktor struktural, seperti ini:

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

Dan kemudian katakan, component ?: ComponentConstructor .

Lebih umum lagi, Anda sebenarnya dapat memiliki jenis fungsi umum:

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

Ini disebut polimorfisme parameterik peringkat-n dan sebenarnya merupakan fitur yang cukup langka dalam bahasa. Jadi, lebih membingungkan lagi mengapa TypeScript tidak memiliki tipe yang lebih tinggi, yang merupakan fitur yang jauh lebih umum.

Batasan yang dibahas di sini akan muncul jika Anda perlu mereferensikan TComponent<T, S> . Tetapi dalam kasus Anda ini tampaknya tidak perlu.


Anda juga dapat menggunakan typeof Component yang akan memberi Anda tipe konstruktor Component tetapi ini akan menyebabkan berbagai masalah dengan subtipe.

@GregRos Solusi yang Anda usulkan tampak menjanjikan (ini menerima tipe di dalam file definisi), tetapi tipe yang kompatibel ditolak. https://gist.github.com/Silic0nS0ldier/3c379367b5e6b1abd76e4a41d1be8217

@ Silic0nS0ldier Lihat komentar saya di intinya.

@chrisvies Apakah ini berhasil?

interface IdType<T> {
    id: T;
}

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

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

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

@ jack-william Ya. Itu berhasil untuk skenario saya. Saya belum menemukan contoh itu di dokumen (meskipun saya dikenal karena banyak hal yang terlewat!). Terima kasih!

Saya telah banyak memikirkan fitur ini, dan saya memiliki beberapa pemikiran tentang masalah tersebut, condong ke beberapa jenis spesifikasi, tetapi saya masih dapat melihat banyak masalah. Saran saya sedikit berbeda dengan yang sudah diajukan selama ini.


Pertama-tama, saya pikir menggunakan sintaks T<*, *> untuk konstruktor tipe adalah ide yang buruk karena tidak berskala baik dengan kompleksitas konstruktor tipe. Saya juga tidak yakin apakah ada gunanya menentukan jenis konstruktor tipe setiap kali dirujuk, karena kami tidak melakukan ini untuk fungsi, bahkan untuk fungsi dengan parameter tipe.

Saya pikir cara terbaik untuk mengimplementasikan ini adalah dengan memperlakukan tipe yang lebih tinggi seperti tipe lainnya, dengan nama biasa, dan mendefinisikan hubungan subtipe yang baik atas konstruktor tipe itu sendiri yang dapat digunakan untuk memaksakan batasan.

Saya pikir kita harus menggunakan semacam awalan atau postfix untuk membedakan mereka dari tipe lain, terutama untuk melindungi pengguna dari pesan kesalahan yang tidak dapat dipahami yang melibatkan konstruktor tipe ketika mereka hanya ingin menulis kode biasa. Saya suka tampilan: ~Type, ^Type, &Type atau sesuatu seperti itu.

Jadi misalnya, tanda tangan fungsi mungkin:

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

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

(Saya tidak menggunakan awalan ~ untuk tipe yang dibangun dengan sengaja)

Dengan menggunakan extends sini pada dasarnya saya telah mengatakan dua hal:

** 1. Jika perlu: ~L adalah konstruktor tipe yang memiliki jenis yang sama dengan ~List , yaitu jenis * -> * (atau mungkin * => * , karena => adalah panah TypeScript).

  1. ~L adalah subtipe dari ~List . **

Menggunakan extends untuk menunjukkan jenis skala konstruktor tipe kompleks sewenang-wenang, termasuk hal-hal seperti ((* => *) => (* => *)) => * .

Anda tidak dapat benar-benar melihat jenis itu di pengenal jenis, tapi saya rasa Anda tidak perlu melakukannya. Saya bahkan tidak yakin hubungan subtipe antara konstruktor tipe harus mempertahankan jenis, jadi (1) mungkin tidak diperlukan.

Tidak ada jenis konstruksi yang tidak lengkap

Saya pikir kita seharusnya tidak mendukung pembuatan tipe yang tidak lengkap. Artinya, sesuatu seperti ini:

(*, *) => * => *

Saya pikir itu akan menciptakan lebih banyak masalah daripada nilainya. yaitu setiap tipe konstruktor harus membangun beberapa tipe beton, dan tipe beton tersebut harus dispesifikasikan dalam konteks di mana TC didefinisikan.

Cara struktural untuk mendefinisikan tipe konstruktor

Saya juga berpikir harus ada cara struktural untuk menentukan konstruktor tipe, dengan cara yang sama tipe lain dapat ditentukan secara struktural, termasuk tipe fungsi generik tingkat tinggi. Saya telah memikirkan sintaks seperti:

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

Yang mirip dengan sintaks yang ada untuk tipe fungsi dengan parameter tipe:

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

Keduanya bahkan dapat digabungkan untuk mendapatkan ini:

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

Yang merupakan tipe konstruktor yang membangun tipe fungsi generik.

Manfaatnya adalah tipe struktural ini dapat digunakan saat menentukan tipe struktural lainnya, saat menentukan batasan tipe, dan seterusnya. Terkadang ini berarti mereka dapat menggunakan simbol lokal referensi yang tidak dapat dirujuk dari tempat lain.

Berikut ini contohnya:

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

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

Dalam contoh di atas, konstruktor tipe struktural ~<A>List<A, B> mereferensikan parameter tipe B . Tidak mungkin untuk menentukan hubungan ini dengan cara lain, setidaknya tanpa pengkodean tipe yang dibuat sebagian List<A, *> . Ada contoh lain juga.

Hubungan subtipe

Hubungan subtipe tampaknya masuk akal, tetapi saya mengalami sejumlah kesulitan saat mencoba mencirikannya.

Ide pertama saya adalah sebagai berikut. Untuk ~A menjadi subtipe dari ~B :

  1. (a) Mereka harus memiliki jenis yang sama (dalam istilah arity, bukan kendala).
  2. (b) Untuk setiap parameterisasi legal T₁, T₂, ... dari ~A , A<T₁, T₂, ...> harus merupakan subtipe dari B<T₁, T₂, ...> .

Namun, ini memiliki beberapa keterbatasan.

  1. class MySpecialPromise mengimplementasikan PromiseLike {} Dalam kasus ini, ~MySpecialPromise bukan merupakan subtipe dari ~PromiseLike karena keduanya memiliki jenis yang berbeda.

  2. kelas MyArrayPromisemengimplementasikan PromiseLike

    Hubungan subtipe juga tidak dipertahankan dalam kasus ini.

Versi yang lebih umum dari (b) adalah sebagai berikut:

(b) Untuk setiap parameterisasi legal T₁, T₂, ... dari ~A , terdapat parameterisasi S₁, S₂, ... dari ~B sehingga A<T₁, T₂, ...> adalah subtipe dari B<S₁, S₂, ...> .

Dengan kata lain, ada pemetaan F (T₁, T₂, ...) = S₁, S₂, ... dengan sifat-sifat di atas. Pemetaan ini harus digunakan untuk membangun parameter B<...> dari parameter A<...> . Ini memungkinkan kita untuk melakukan ini bahkan jika konstruktor tipe memiliki jenis yang berbeda.

Masalah dengan relasi ini adalah saya tidak yakin bagaimana mungkin menemukan pemetaan yang benar. Dalam bahasa dengan pengetikan nominal, setiap pernyataan di sepanjang baris:

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

Mendefinisikan pemetaan antara parameter tipe ~A dan parameter tipe ~B , jadi ini adalah bagaimana pemetaan dapat dipulihkan. Namun, dalam sistem pengetikan struktural TypeScript, kami tidak dapat mengandalkan pernyataan eksplisit jenis ini.

Salah satu caranya adalah dengan hanya mendukung konstruktor tipe untuk tipe dengan tipe informaiton yang tepat, seperti implements klausa atau sejenis anggota tipe abstrak yang mirip dengan Scala. Saya tidak yakin apakah ini jalan ke depan.

@GregRos - Catatan Menarik! Beberapa pertanyaan.


Apa yang Anda maksud dengan tipe beton? Apakah yang Anda maksud dengan jenis * , atau jenis tanpa parameter jenis terikat?


Tidak ada jenis konstruksi yang tidak lengkap
Saya pikir kita seharusnya tidak mendukung pembuatan tipe yang tidak lengkap. Artinya, sesuatu seperti ini:
(*, *) => * => *

Apa yang Anda maksud dengan membangun tipe yang tidak lengkap? Maksud Anda, setiap aplikasi seperti L<A> harus memiliki jenis * . Apakah konstruktor jenis pasangan khusus dalam contoh Anda, misalnya, apakah (* => *) => * => * akan baik-baik saja?


Cara struktural untuk mendefinisikan tipe konstruktor

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

Apakah contoh ini berbeda, selain yang pertama anonim?


Hubungan subtipe

~A dan ~B tidak mengacu pada tipe jadi apakah masuk akal bagi mereka untuk memiliki hubungan subtipe? Kapan Anda benar-benar perlu memeriksa bahwa satu konstruktor adalah 'subtipe' dari yang lain? Apakah mungkin menunggu sampai konstruktor diterapkan dan memeriksa jenis yang dihasilkan?

@ jack-williams Terima kasih atas tanggapannya!

Apa yang Anda maksud dengan membangun tipe yang tidak lengkap? Maksud Anda, setiap aplikasi seperti L<A> harus memiliki jenis *. Apakah konstruktor jenis pasangan khusus dalam contoh Anda, misalnya, apakah (* => *) => * => * akan baik-baik saja?

Ya persis. Setiap aplikasi seperti L<A> harus memiliki jenis * . Saya tidak yakin bagaimana saya menjualnya.


Apakah contoh ini berbeda, selain yang pertama anonim?

Yang pertama adalah ekspresi tipe, sedangkan yang kedua adalah deklarasi. Mereka identik dalam banyak hal, dengan cara yang sama bahwa tipe-tipe ini identik dalam banyak hal:

{
     a : number;
     b : string;
}

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

Sintaksnya memiliki beberapa motivasi:

  1. Sama seperti semua hal lain di TypeScript, ini memungkinkan konstruktor tipe ditentukan secara struktural dan anonim.
  2. Ekspresi tipe (seperti objek anonim yang diketik di atas) dapat digunakan dalam konteks tertentu di mana pernyataan deklarasi tidak dapat digunakan, seperti dalam tipe tanda tangan fungsi. Ini memungkinkan mereka untuk menangkap pengenal lokal dan mengekspresikan hal-hal yang tidak dapat diungkapkan sebaliknya.

~A dan ~B tidak mengacu pada tipe, jadi apakah masuk akal jika mereka memiliki relasi subtipe? Kapan Anda benar-benar perlu memeriksa bahwa satu konstruktor adalah 'subtipe' dari yang lain? Apakah mungkin menunggu sampai konstruktor diterapkan dan memeriksa jenis yang dihasilkan?

Tipe konstruktor mungkin atau mungkin tidak dianggap sebagai tipe. Saya mengusulkan untuk menganggapnya sebagai tipe, hanya yang tidak lengkap yang tidak memiliki nilai dan tidak dapat muncul dalam konteks apa pun yang membutuhkan tipe nilai. Filosofi yang sama diambil oleh Scala, dalam dokumen ini

Dengan relasi subtipe, yang saya maksud pada dasarnya adalah semacam relasi "konformitas" yang dapat digunakan untuk membatasi konstruktor tipe. Misalnya, jika saya ingin menulis fungsi yang bekerja pada semua jenis janji dari berbagai jenis, seperti Promise<T> , Bluebird<T> , dan seterusnya, saya memerlukan kemampuan untuk membatasi parameter TC dengan antarmuka PromiseLike<T> dalam beberapa cara.

Kata alami untuk jenis relasi ini adalah relasi subtipe.

Mari kita lihat contohnya. Dengan asumsi kita telah mengerjakan hubungan subtipe antara konstruktor tipe, saya dapat menulis fungsi seperti ini:

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

Dan batasan ~P extends ~PromiseLike seharusnya menjamin ini adalah fungsi yang bekerja pada promise, dan hanya promise. Batasan juga akan menjamin bahwa di dalam tubuh fungsi, promise akan diketahui untuk mengimplementasikan PromiseLike<A> , dan seterusnya. Setelah semua, anggota diakui oleh naskah dalam tubuh fungsi adalah justru orang yang bisa terbukti ada melalui kendala.

Dengan cara yang sama Promise<T> extends PromiseLike<T> , karena mereka secara struktural kompatibel dan dapat diganti satu sama lain, ~Promise extends ~PromiseLike karena mereka membangun tipe yang kompatibel secara struktural dan dengan demikian dapat diganti satu sama lain.


Untuk menggarisbawahi masalah dengan masalah subjenis, pertimbangkan sekali lagi:

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

Bisakah kita mengabstraksi lebih dari ~MyPromise dengan cara yang sama kita mengabstraksi lebih dari ~Promise ? Bagaimana kita menangkap hubungan di antara mereka?

Pemetaan yang saya bicarakan sebelumnya adalah pemetaan yang, dengan parameterisasi ~MyPromise , akan menghasilkan parameterisasi ~Promise sehingga tipe yang dibuat oleh ~MyPromise adalah subtipe dari satu dibangun dengan ~Promise .

Dalam hal ini, pemetaannya seperti ini:

T => T[]

@Greg

Dalam kasus ini, ~MySpecialPromise bukan merupakan subtipe dari ~PromiseLike karena keduanya memiliki jenis yang berbeda.

Di Haskell, masalah semacam ini diselesaikan dengan mengizinkan penerapan sebagian tipe, dan menentukan tipe sehingga parameter tipe terakhir bertepatan dengan parameter tipe dari antarmuka apa pun yang Anda terapkan.

Dalam contoh Anda, MySpecialPromise akan didefinisikan sebagai MySpecialPromise<TSpecial, TPromiseVal> , dan ~MySpecialPromise<SpecialType> akan memiliki jenis yang identik dengan ~Promise .

@Greg

Dengan relasi subtipe, yang saya maksud pada dasarnya adalah semacam relasi "konformitas" yang dapat digunakan untuk membatasi konstruktor tipe. Misalnya, jika saya ingin menulis fungsi yang berfungsi pada semua jenis promise dari berbagai jenis, seperti Promise, Burung biru, dan seterusnya, saya memerlukan kemampuan untuk membatasi parameter TC dengan antarmuka PromiseLikedalam beberapa hal.
function mapPromise<~P extends ~PromiseLike, A, B>(promise : P<A>, func : (x : A) => B) : P<B> ;

Saya pikir ketika datang untuk memeriksa jenis fungsi itu Anda akan mencoba dan menyatukan BlueBird<T> dan PromiseLike<T> untuk T , ini hanya tipe konkret dan termasuk dalam subtipe. Saya tidak mengerti mengapa Anda memerlukan relasi khusus untuk konstruktor ~BlueBird dan ~PromiseLike .

Saya kira itu akan digunakan dalam hal seperti ini?


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

Di sini Anda mungkin ingin memeriksa bahwa batasan y menyiratkan batasan x, tetapi apakah TypeScript belum memiliki mesin untuk memeriksa bahwa BlueBird<T> extends PromiseLike<T> yang dapat digunakan?

@ jack-williams Bergantung pada bagaimana Anda menentukan batasan berikut:

~ P adalah tipe konstruktor sehingga, untuk semua A , P<A> adalah subtipe dari PromiseLike<A> .

Jenis sintaks apa yang akan Anda gunakan? Konsep seperti apa yang akan Anda gunakan? Anda bisa menulis sesuatu seperti ini:

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

Tetapi sintaks ini memiliki batasan. Misalnya, Anda tidak dapat mengekspresikan kelas ini sama sekali, karena kita tidak dapat membuat tipe P<A> pada titik di mana ia dideklarasikan untuk membatasinya:

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

Tapi saya rasa Anda bisa menggunakan tipe eksistensial untuk itu, seperti ini:

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

Kemudian Anda dapat meminta semua tipe konstruktor dibatasi melalui tipe yang dibangun di dalam tanda tangan fungsi atau tipe, secara opsional menggunakan tipe eksistensial.

Dengan tipe eksistensial, ini akan memiliki kekuatan ekspresif yang sama sebagai relasi subtipe dengan pemetaan.

Namun, ini akan memiliki banyak masalah:

  1. Menentukan tipe konstruktor dengan jenis seperti ((* => *) => *) => * akan membutuhkan pengenalan banyak tipe eksistensial, beberapa di antaranya harus berorde lebih tinggi. Semuanya harus menjadi ujung tombak dalam tanda tangan fungsi atau kelas.
  2. Saya tidak sepenuhnya yakin akan lebih mudah menemukan tipe eksistensial yang dimaksud daripada menemukan pemetaan.
  3. Saya pikir itu kurang elegan dibandingkan hubungan subtipe.
  4. Secara potensial memperkenalkan bentuk tipe lain yang harus Anda tangani.

@Greg

Jenis sintaks apa yang akan Anda gunakan? Konsep seperti apa yang akan Anda gunakan?

_Personally_ Saya tidak akan menggunakan sintaks khusus dan hanya menggunakan:

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

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

tetapi ini hanya pendapat saya karena saya melihat hal-hal seperti number sebagai konstruktor null-ary: jadi tidak perlu ada perbedaan.

Pandangan saya tentang subtipe untuk fungsi konstruktor adalah membuatnya sesederhana mungkin. Mereka harus memiliki arity yang sama dan parameternya harus sub-jenis satu sama lain, dengan mempertimbangkan kontradiksi dan kovarian seperti makalah Scala.

Aplikasi parsial dapat mengatasi kasus-kasus di mana mereka memiliki arity yang berbeda (saya tidak keberatan auto-curryng untuk konstruktor tipe sehingga Anda dapat menulis MySpecialPromise<SpecialType> ).

Dalam contoh interface MyPromise<T> extends Promise<T[]> {} saya harus jujur ​​dan mengatakan bahwa saya tidak yakin bahwa menangani kasus ini sepadan dengan kerumitannya - saya pikir ini akan menjadi fitur yang cukup berguna tanpanya.

Menangani kasus itu sama dengan (menurut saya), mengatakan: ~MyPromise extends ~(Promise . []) dimana [] adalah konstruktor daftar dan . adalah komposisi konstruktor. Hal ini sepertinya menjadi jauh lebih sulit karena sekarang tidak cukup hanya dengan memeriksa struktur konstruktor, tetapi Anda juga harus memikirkan tentang komposisi!

@ jack-williams Ini tidak bekerja dengan parameter tipe default. Jika saya menulis P extends Foo , dimana Foo memiliki parameter tipe default, yaitu type Foo<T = {}> = ... , lalu apa jenis P ?

Saya hanya ingin mengatakan bahwa saya menyetujui tipe tingkat tinggi (saya pernah mengalami situasi dalam proyek TypeScript nyata di mana mereka akan berguna).

Namun menurut saya mereka tidak harus mendukung kari. Saya suka Haskell, tapi itu tidak cocok dengan TypeScript.

Tipe tingkat tinggi berguna bahkan tanpa aplikasi kari atau parsial, tetapi jika aplikasi parsial diperlukan, saya lebih suka melihat sintaks eksplisit untuk itu. Sesuatu seperti ini:

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

@ cameron-martin

Edit: Maaf, menurut saya komentar saya _tidak_ terlalu jelas. Dengan P memiliki jenisnya sendiri, maksud saya ia memiliki jenis yang dipaksakan oleh penggunaannya. Katakanlah batasan selalu diasumsikan sebagai jenis tertinggi, jadi Foo diasumsikan ~Foo . Hanya jika kita memaksa P menjadi jenis yang lebih rendah kita memeriksa apakah Foo memiliki parameter default. Perhatian saya dengan ini adalah inferensi yang baik, tetapi dalam kasus itu ~ tidak akan membantu dan saya pikir kita perlu penjelasan lengkap.

P memiliki jenisnya sendiri, bukan? Bukankah pertanyaannya adalah apakah kita memperlakukan Foo sebagai ~Foo , atau sebagai Foo<{}> : Saya berpendapat itu akan didorong oleh jenis P. Jadi jika P adalah tipe yang kita paksakan parameter defaultnya, dan jika P adalah konstruktor * => * , maka kita memperlakukan Foo sama.

@Pauan Setuju dengan saran Anda.

@ jack-williams Saya telah mempertimbangkan gagasan subtipe itu, seperti yang saya sebutkan sebelumnya:

Ide pertama saya adalah sebagai berikut. Untuk ~A menjadi subtipe dari ~B :

  1. (a) Mereka harus memiliki jenis yang sama (dalam istilah arity, bukan kendala).
  2. (b) Untuk setiap parameterisasi legal T₁, T₂, ... dari ~A , A<T₁, T₂, ...> harus merupakan subtipe dari B<T₁, T₂, ...> .

Masalahnya adalah jika kita membuat semuanya sesederhana mungkin, kita akan berakhir dengan hubungan subtipe yang paradoks dan tidak sesuai dengan bahasanya.

Jika MyPromise<T> extends Promise<T[]> itu berarti MyPromise<T> harus dapat digunakan dimanapun Promise<T[]> dapat digunakan, tapi ini tidak lagi menjadi masalah.

Jika Anda menggunakan as untuk mengubah a : MyPromise<T> menjadi Promise<T[]> , Anda akan melakukan upcasting, tetapi ini secara paradoks akan membuat a lebih dapat dialihkan.

Batasan umum yang ada, yang mengikuti relasi subtipe yang ada, juga dapat digunakan untuk mencapai efek serupa dan menyebabkan perilaku aneh:

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

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

Mengetik akan menjadi setidaknya sebagian nominal sebagai efek samping juga. Jenis-jenis ini tiba-tiba akan berbeda, di mana mereka saat ini sama:

type GenericNumber<T> = number;

type RegularNumber = number;

Saya bahkan tidak yakin apa efeknya pada tipe persatuan / persimpangan kompleks dengan parameter tipe, tipe struktural murni, tipe dengan pemahaman anggota, dan sejenisnya.

Perasaan pribadi saya adalah bahwa: Hubungan subtipe atas konstruktor tipe perlu menghormati yang sudah ada, bukan menentangnya . Sayangnya, hal ini membutuhkan hal-hal yang lebih kompleks.


Alasan terbesar untuk menggunakan beberapa jenis notasi khusus untuk konstruktor tipe adalah karena 99% pengembang tidak tahu apa itu konstruktor tipe dan tidak ingin dibombardir dengan pesan kesalahan tentang mereka.

Ini sangat berbeda dengan Haskell, di mana setiap pengembang diwajibkan oleh hukum untuk mengambil kursus lanjutan dalam teori kategori.

Alasan sekunder adalah bahwa dalam beberapa kasus (seperti kasus parameter default yang disebutkan di atas), sintaks akan menjadi ambigu atau tidak mungkin untuk mengabstraksi sama sekali pada konstruktor tipe tertentu.

EDIT: Maaf @GregRos Saya tidak melihat komentar terakhir Anda!

Relasi subtipe atas konstruktor tipe perlu menghormati yang sudah ada, bukan menentangnya.

Jika ini bisa tercapai maka saya setuju. Saya hanya tidak memikirkan semua detail dan betapa mudahnya ini.

Alasan sekunder adalah bahwa dalam beberapa kasus (seperti kasus parameter default yang disebutkan di atas), sintaks akan menjadi ambigu atau tidak mungkin untuk mengabstraksi sama sekali pada konstruktor tipe tertentu.

Saya tidak yakin saya setuju bahwa itu akan menjadi ambigu jika Anda selalu mengasumsikan jenis batasan tertinggi sampai Anda membutuhkannya lebih rendah. Ini bukan pernyataan dan jika ada contoh lain yang menunjukkan sebaliknya maka cukup adil.


Masalahnya adalah jika kita membuat semuanya sesederhana mungkin, kita akan berakhir dengan hubungan subtipe yang paradoks dan tidak sesuai dengan bahasanya.

Itu mungkin benar, saya kira saya hanya khawatir, apakah alternatif itu mungkin untuk benar-benar diterapkan. Untuk apa nilainya, jika solusi yang lebih kompleks berhasil, itu akan bagus!

Memiliki pengertian yang lebih umum tentang subtipe yang menunjukkan keberadaan fungsi pemetaan tampaknya sulit untuk diterapkan secara umum. Apakah contoh saya berikut menafsirkan aturan Anda dengan benar?

(b) Untuk setiap parameterisasi legal T₁, T₂, ... dari ~ A, terdapat parameterisasi S₁, S₂, ... dari ~ B sedemikian rupa sehingga A

Apakah X akan menjadi subtipe dari Y dalam kasus berikut, diberi pemetaan F (A, B) = (number, B).

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

Namun X<string,number> tidak akan menjadi subtipe dari Y<string,number> .

Saya kira saya tidak jelas apakah _existence_ dari pemetaan sudah cukup. Jika kita mengambil ~ A dan ~ B sebagai fungsi, dan kita ingin menunjukkan bahwa ~ B mendekati ~ A, atau ~ A adalah subtipe dari ~ B, lalu menunjukkan bahwa ada beberapa fungsi ~ C, sehingga ~ A adalah a subtipe dari (~ B. ~ C), menurut saya tidak cukup (C adalah mapper). Saya harus menjadi kasus untuk _all_ pemetaan.

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

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

Saya kurang mengikuti contoh ini, haruskah kesalahan di sini tidak terjadi? Pembacaan saya tentang ini adalah bahwa id1 harus memiliki input yang dibangun oleh fungsi P yang memberikan PromiseLike untuk semua _inputs_. Sedangkan id2 berbicara tentang nilai yang harus menjadi subtipe dari penerapan Promise to A []. Saya tidak yakin apakah mungkin memulihkan informasi yang diperlukan untuk id1 , dari jenis id2 . Saya pikir saya mungkin salah memahami maksud Anda.

Jenis-jenis ini tiba-tiba akan berbeda, di mana mereka saat ini sama

Sekali lagi, saya khawatir saya mungkin kehilangan maksud Anda tetapi tidak tahu bagaimana mereka sama. Saya tidak dapat mengganti RegularNumber dengan GenericNumber dalam sebuah tipe, saya harus memberikan argumen yang terakhir.

Saya kira saya tidak jelas apakah keberadaan pemetaan sudah cukup. Jika kita mengambil ~ A dan ~ B sebagai fungsi, dan kita ingin menunjukkan bahwa ~ B mendekati ~ A, atau ~ A adalah subtipe dari ~ B, lalu menunjukkan bahwa ada beberapa fungsi ~ C, sehingga ~ A adalah a subtipe dari (~ B. ~ C), menurut saya tidak cukup (C adalah mapper). Saya harus menjadi kasus untuk semua pemetaan.

Yup, Anda benar, dan begitu juga contoh balasan yang Anda berikan. Saya telah menemukan contoh tandingan lainnya. Tidak bekerja sama sekali.

Saya telah membaca ulang utas ini dan banyak balasan Anda. Saya pikir Anda benar dalam banyak hal dan saya telah melihat masalahnya dengan cara yang salah. Saya akan mendapatkan apa yang saya maksud.

Saya tidak yakin saya setuju bahwa itu akan menjadi ambigu jika Anda selalu mengasumsikan jenis batasan tertinggi sampai Anda membutuhkannya lebih rendah. Ini bukan pernyataan dan jika ada contoh lain yang menunjukkan sebaliknya maka cukup adil.

Entah itu ambigu atau sesuatu menjadi tidak mungkin untuk dirujuk. Seperti pada contoh di atas, tipe konstruktor dari Foo menjadi tidak mungkin untuk referensi karena disembunyikan oleh tipe itu sendiri. Jika Anda menulis ~Foo atau dalam hal ini Foo<*> atau ~<A>Foo<A> atau apa pun yang tidak bertentangan dengan hal lain, Anda tidak akan mengalami masalah seperti ini.

Ya, Anda bisa menyiasatinya dengan mendefinisikan alias, meskipun itu tidak terlalu bagus:

type Foo2<T> = Foo<T>

Seperti yang saya katakan, saya rasa ini bukan masalah yang paling penting.

Saya kurang mengikuti contoh ini, haruskah kesalahan di sini tidak terjadi? Saya membaca ini adalah bahwa id1 harus memiliki masukan yang dibangun oleh fungsi P yang memberikan PromiseLike untuk semua masukan. Sedangkan id2 berbicara tentang nilai yang harus menjadi subtipe dari penerapan Promise to A []. Saya tidak yakin apakah mungkin untuk memulihkan informasi yang diperlukan untuk id1, dari jenis id2. Saya pikir saya mungkin salah memahami maksud Anda.

Itu bacaan yang benar, ya. Tetapi jika P extends Promise<A[]> itu harus dapat dialihkan ke tempat mana pun yang menerima Promise<A[]> , seperti id1 . Beginilah keadaannya sekarang, dan apa arti subtipe.

Saya tidak berpikir itu bisa dihindari lagi.

Sekali lagi, saya khawatir saya mungkin kehilangan maksud Anda tetapi tidak tahu bagaimana mereka sama. Saya tidak dapat mengganti RegularNumber dengan GenericNumber dalam suatu tipe, saya harus memberikan argumen yang terakhir.

Yang saya maksud adalah: tipe GenericNumber<T> , untuk semua T , dan tipe RegularNumber adalah identik dan dapat dipertukarkan. Tidak ada konteks di mana yang satu akan mengetik centang dan yang lainnya tidak. Setidaknya sekarang.

Apa yang kita bicarakan akan membuat mereka berbeda. Karena GenericNumber<T> berasal dari TC, ini dapat digunakan di tempat yang tidak memungkinkan RegularNumber . Jadi tidak bisa lagi dipertukarkan.

Saya telah memikirkan hal ini, dan saya rasa itu mungkin tidak dapat dihindari, dan belum tentu buruk. Hanya perilaku baru yang berbeda.

Salah satu cara untuk memikirkannya adalah bahwa parameter tipe menjadi bagian dari "struktur" tipe.

Menurut saya, TC akan menghasilkan perilaku yang lebih berbeda.

Arah baru

Pertama-tama, saya pikir Anda benar bahwa hubungan subtipe yang benar adalah yang tidak memiliki pemetaan:

Ide pertama saya adalah sebagai berikut. Untuk ~A menjadi subtipe dari ~B :

  1. (a) Mereka harus memiliki jenis yang sama (dalam istilah arity, bukan kendala).
  2. (b) Untuk setiap parameterisasi legal T₁, T₂, ... dari ~A , A<T₁, T₂, ...> harus merupakan subtipe dari B<T₁, T₂, ...> .

Masalah pemetaan ... sejujurnya, itu sangat bodoh. Saya tidak berpikir ada cara untuk menyatukan MyPromise<T> extends Promise<T[]> dan ~Promise lagi. Saya ingin tahu apakah seseorang berpikir sebaliknya.

Saya juga ingin mengetahui apakah ada contoh yang saya lewatkan di mana bahkan aturan ini tidak berfungsi.

Jika kita setuju bahwa batasan konstruktor tipe harus diekspresikan menggunakan relasi subtipe, yang tampaknya bekerja dengan sangat baik, kita dapat pindah ke hal lain.

Tentang sintaks

Kami tidak benar-benar setuju tentang hal ini, tampaknya. Saya sangat menyukai sintaks prefiks yang mirip dengan ~Promise . Secara konseptual, ~ dapat dilihat sebagai operator "referensi ke TC" atau semacamnya.

Saya pikir saya telah memberikan beberapa alasan mengapa ini lebih baik daripada alternatif:

  1. Sangat tidak ambigu.

    1. Kesalahan yang melibatkan sintaks ini juga tidak ambigu. Jika seseorang lupa untuk membuat parameter tipe, mereka tidak akan mendapatkan kesalahan tentang konstruktor tipe ketika mereka tidak tahu apa itu.

    2. Sebagai efek samping, teks dan logika kesalahan yang ada tidak perlu diubah. Jika seseorang menulis Promise pesan kesalahannya akan sama persis seperti sekarang. Tidak perlu berubah untuk membicarakan TC.

  2. Memperluas dengan baik ke sintaks struktural.
  3. Mudah diurai, saya yakin. Setiap ~\w muncul di mana tipe diharapkan akan dianggap menunjukkan referensi ke TC.
  4. Mudah diketik.

Saya berharap orang lain bisa memberikan pendapatnya.

Tentang serikat pekerja, persimpangan, dan parameter jenis default

Apakah formulir yang kelebihan beban / campuran * & (* => *) , * | (* => *) , dan seterusnya, legal? Apakah mereka memiliki kegunaan yang menarik?

Saya pikir itu adalah ide yang buruk dan sulit untuk dipikirkan. Saya juga tidak yakin jenis anotasi apa yang Anda perlukan untuk membedakan * | (* => *) sehingga Anda dapat membuat jenis darinya.

Salah satu cara agar jenis seperti itu dapat dikatakan ada saat ini, adalah jenis dengan parameter jenis default:

type Example<A = number> = {}

Tipe ini dapat dikatakan memiliki jenis * & (* => *) karena dapat menerima parameter tipe untuk membangun sebuah tipe, tetapi tidak harus.

Saya percaya bahwa parameter tipe default harus dalam bentuk singkatan, bukan cara untuk mendeskripsikan tipe. Jadi menurut saya parameter tipe default sebaiknya diabaikan saat menentukan jenis tipe.

Namun, mungkin masuk akal untuk membicarakan jenis seperti ~Promise | ~Array . Mereka memiliki jenis yang sama, jadi mereka tidak bertentangan. Saya pikir ini harus didukung.

Hal-hal yang harus ditangani

Situasi terkait harus ditangani, seperti situasi ini:

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

Tetapi ini tidak benar-benar melibatkan jenis (* => *) | (*, *) => * , tetapi sesuatu yang berbeda

Membangun TC lain?

Seperti yang saya sebutkan sebelumnya, menurut saya bukan ide yang baik memiliki TC yang membuat TC lain, seperti * => (* => *) . Mereka adalah norma dalam bahasa yang mendukung kari dan sejenisnya, tetapi tidak dalam TypeScript.

Tidak ada cara yang dapat saya lihat untuk mendefinisikan tipe seperti itu sama sekali menggunakan sintaks ~ dan relasi subtipe, jadi tidak memerlukan aturan khusus untuk melarangnya. Diperlukan aturan khusus untuk membuatnya berfungsi.

Saya rasa Anda bisa mendefinisikannya secara struktural seperti ini:

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

Itulah satu-satunya cara saya berpikir.

Interaksi dengan fungsi peringkat lebih tinggi?

Ada interaksi alami, tetapi kompleks dengan jenis fungsi yang mengambil parameter jenis:

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

Haruskah interaksi ini dihentikan? Saya bisa melihat tipe seperti ini menjadi sangat rumit.

Secara umum, adakah tempat parameter TC tidak boleh muncul?

Apakah sintaks struktural saya ide yang bagus?

Saya tidak berpikir itu harus segera diimplementasikan, tetapi menurut saya sintaks struktural saya adalah ide yang bagus. Ini memungkinkan Anda:

  1. Gunakan pengenal lokal seperti parameter tipe lain dalam batasan Anda.
  2. Terapkan sebagian konstruktor tipe, dengan cara yang sangat eksplisit dan fleksibel: ~<A>Map<string, A> , ~<A, B>Map<B, A> , dan seterusnya.
  3. Setiap aspek lain dari suatu tipe memiliki sintaks struktural, jadi TC juga harus memiliki sintaks tersebut.

Meskipun demikian, TC dapat sepenuhnya bekerja tanpa ini, dan PR pertama mungkin tidak akan melibatkan mereka.

Interaksi dengan tipe bersyarat

Bagaimana cara kerja fitur dengan tipe bersyarat? Haruskah Anda bisa melakukan ini?

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

Saya sendiri tidak sepenuhnya yakin. Masih belum sepenuhnya mencerna tipe kondisional.

Resolusi yang berlebihan

Saya merasa ini akan sulit dilakukan. Ini sebenarnya sangat penting karena bentuk resolusi berlebih yang berbeda akan menghasilkan jenis yang berbeda.

Meskipun demikian, saya tidak dapat memberikan contoh yang baik sekarang.

Anda tahu, sebagian besar dari ini akan menjadi titik perdebatan jika bahasa perantara yang didefinisikan dengan baik digunakan untuk mendeskripsikan TypeScript sebagai titik awal. Misalnya: Sistem F <: atau salah satu sistem jenis yang bergantung pada suara seperti ML yang Disederhanakan Tergantung .

Sejujurnya saya akan terkejut jika ini diselesaikan sebelumnya
https://github.com/Microsoft/TypeScript/issues/14833

Saya merasa # 17961 mungkin bisa menyelesaikan ini secara tidak langsung. Lihat inti ini untuk lebih jelasnya.

Perhatikan bahwa tipe Bifunctor dan Profunctor agak rumit pada tingkat kendala - akan jauh lebih sederhana jika saya memiliki tipe universal yang jelas untuk dikerjakan, daripada infer T yang hanya terbatas pada tipe bersyarat. Juga, alangkah baiknya jika saya bisa menggunakan this sebagai tipe "return" (yaitu level tipe murni) - yang akan membuat sebagian besar antarmuka saya lebih mudah untuk didefinisikan.

(Saya bukan pengguna TS yang berat, jadi saya mungkin telah membuat kesalahan. @ Tycho01 Bisakah Anda melihat itu untuk melihat apakah saya mengacaukan di mana saja dalam jenis kekacauan? Alasan saya bertanya adalah karena Anda yang di belakang PR di atas, dan saya telah melihat beberapa eksperimen dan utilitas Anda yang lain.)

@isiaheadows @ tycho01 Wow ...

Kamu benar. Jika saya memahaminya dengan benar, hasilnya hampir sama.

Ada beberapa perbedaan. Tapi secara fungsional, mereka hampir sama, dan menurut saya perbedaan ini bisa diatasi.

Tidak mungkin menyimpulkan fungsi tipe yang benar

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

Di sini Anda dapat menyimpulkan ~Promise dan ~Bluebird dari p . Namun, jika Anda melakukannya seperti ini:

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

Saya sangat ragu bahwa ini akan berhasil:

example(null as Promise<number>)

Tidak ada cara untuk menyimpulkan bahwa F dimaksudkan sebagai:

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

Karena fungsi ini sama sekali tidak dianggap istimewa. Sedangkan dengan TC, beberapa tipe pada dasarnya memiliki fungsi level-tipe "implisit": TC-nya.

Tidak mungkin mereferensikan TC yang ada dengan mudah

Anda tidak dapat melakukan ~Promise seperti dalam proposal saya. Anda harus menyandikan jenisnya secara langsung, menggunakan format struktural:

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

Benar, dan itu menjadi perhatian. Itu lebih merupakan masalah inferensi tipe di mana Anda membutuhkan kemampuan untuk menyimpulkan fungsi generik itu sendiri dari tipe argumen yang diketahui (kebalikan dari apa yang biasanya terjadi). Ini dapat dipecahkan dengan cara yang cukup umum untuk bekerja pada kebanyakan kasus, tetapi membutuhkan kasus khusus baru yang tidak sepele.

Ini mungkin dapat diselesaikan sebagian melalui penggunaan strategis NoInfer<T> , tetapi saya tidak 100% yakin bagaimana hal itu perlu dilakukan, dan seberapa banyak hal itu dapat mengatasi bahkan kasus umum.

@Greg

Saya tidak terlalu mendukung sintaks apa pun, itu lebih hanya preferensi saya, ada banyak manfaat untuk ~ . Saya pikir hal utama yang harus dipertimbangkan adalah apakah sintaks untuk anotasi jenis eksplisit diperlukan karena inferensi tidak selalu memungkinkan.


Masalah pemetaan ... sejujurnya, itu sangat bodoh. Saya rasa tidak ada cara untuk menyatukan MyPromisememperpanjang Janji

Saya pikir hal pemetaan masih bisa menjadi gagasan yang berguna, tetapi dalam kasus di atas saya rasa kita tidak pernah mencoba menyatukan ~MyPromise dengan ~Promise , hal yang perlu disatukan adalah ~MyPromise dan ~<T>Promise<T[]> , yang juga dapat kita tulis ~(Promise . []) . Saya pikir hal yang hilang adalah bahwa pemetaan perlu menjadi bagian dari hubungan subtipe: itu sebanyak Promise konstruktor. Dalam contoh tersebut pemetaan hanyalah konstruktor daftar.

interface A<T> {
    x: T;
} 

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

Apakah ~<T>B<T> memperpanjang ~<T>A<T[]> ? Iya. Apakah ~<T>B<T> memperpanjang ~<T>A<T> ? Tidak. Tapi pada akhirnya mereka adalah dua pertanyaan yang tidak berhubungan.

Jika kita setuju bahwa batasan konstruktor tipe harus diekspresikan menggunakan relasi subtipe, yang tampaknya bekerja dengan sangat baik, kita dapat pindah ke hal lain.

Ya, saya pikir ini sepertinya cara yang bagus untuk menggambarkan sesuatu.


Tidak mungkin menyimpulkan fungsi tipe yang benar

function example<~P extends ~PromiseLike>(p : P<number>) : P<string>;
Di sini Anda dapat menyimpulkan ~Promise dan ~Bluebird dari p.

Ini bukan pernyataan, lebih merupakan pertanyaan terbuka karena saya tidak sepenuhnya yakin bagaimana pemeriksaan tipe bekerja. Saya pikir, dengan menggunakan antarmuka A atas sebagai contoh, jenis A<number> dan {x: number} tidak dapat dibedakan dan oleh karena itu saya tidak yakin apakah mungkin untuk menyimpulkan konstruktor dari jenis yang dikembalikan dari aplikasi konstruktor. Apakah mungkin untuk memulihkan P dari P<number> ? Saya yakin hal-hal dapat diubah untuk mendukung ini, saya hanya ingin tahu apa yang dilakukannya sekarang.

Ada tanggapan silang dari # 17961, tapi sayangnya saya tidak yakin bagaimana cara pendekatan @isiahmeadows berhasil. Saya khawatir kesimpulan terbelakang pada panggilan tipe tidak sepele.

Jadi sepertinya berdasarkan masukan Promise<number> atau Bluebird<number> kami ingin dapat menyimpulkan versi yang tidak diterapkan dari jenis ini sehingga kami dapat menerapkannya kembali dengan misalnya string . Kedengarannya sulit.

Bahkan jika tipe input seperti ini dan bukan beberapa persamaan struktural (kami adalah bahasa yang diketik secara struktural, kan?), Alasan ini juga menjadi kabur jika misalnya Bluebird memiliki dua tipe param sebagai gantinya, pada titik mana <string> jenis aplikasi param mungkin tidak lagi masuk akal.
Saya tidak yakin ada solusi yang bagus di sana. (disclaimer: Saya sedikit ketinggalan diskusi di sini.)

@ tycho01 Apakah semua masalah ini akan hilang jika orang secara eksplisit memberi contoh T ?

Saya pikir itu masuk akal, mengingat saya ragu kesimpulan dapat diselesaikan untuk semua kasus.

@ jack-williams: sejauh ini tidak dengan # 17961, tapi saya pikir menggunakannya untuk pengiriman mungkin membantu:

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

@ tycho01 Ya, saya menyadari saran saya sangat buruk karena T tidak dipakai pada pemanggilan metode.

Apakah sesuatu seperti berikut ini berhasil?

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

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

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

@ jack-williams Saya kira pertanyaannya adalah bagaimana hal itu akan dibandingkan dalam perilaku implementasi ADT di fp-ts , tapi sepertinya ini bisa berhasil, ya. Mungkin juga tanpa TyCon .

@ jack-williams @isiahmeadows :

Saya mencoba ide tersebut pada alur percobaan , karena sudah tersedia $Call . Bagi saya tampaknya menjadi tidak responsif entah bagaimana ...

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

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

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

@ tycho01 Saya rasa Anda tidak bisa begitu saja mendapatkan properti dengan $ElementType dari aliran this

@ tycho01 Anda sebenarnya tidak bisa mendapatkan ini untuk bekerja di skrip juga
taman bermain: https://goo.gl/tMBKyJ

@goodmind : hm, sepertinya ini menyimpulkan Maybe<number> bukan Functor<number> setelah menyalin lebih dari fmap dari Functor menjadi Maybe .
Dengan panggilan tipe saya rasa itu akan meningkatkan menjadi hanya tipe di sana daripada membutuhkan implementasi run-time untuk tipe tersebut.
Sekarang, para functor sudah membutuhkan implementasi fmap mereka sendiri. Itu akan menyedot metode turunannya .
Kembali ke titik awal. : /

Saya berencana untuk merilis versi alfa SECEPATNYA, tetapi Anda dapat mengikuti saya menulis contoh dalam masalah itu untuk merasakannya.

Masalah khusus ini agak lama untuk diselesaikan seluruhnya, tetapi yang saya cari sederhana terkandung, tetapi contoh nyata dari kode yang tidak dapat Anda ketik karena kurangnya jenis generik berparameter. Saya rasa saya dapat mengetik sebagian besar dari mereka (asalkan mereka tidak bergantung pada konstruktor tipe angkat abstrak). Jangan ragu untuk membuka masalah di repo di atas dengan kode dan saya akan mengetikkannya untuk Anda jika saya bisa (atau Anda dapat mempostingnya di sini juga).

Perhatian Saya sudah mulai mencoba menerapkan ini di # 23809. Ini masih sangat tidak lengkap tapi lihatlah jika Anda tertarik.

Saya berjanji kepada kalian sebuah contoh sederhana, ini dia. Ini menggunakan beberapa trik yang saya pelajari dari menulis perpustakaan saya.

type unknown = {} | null | undefined;

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

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

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

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

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

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

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

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


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

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

Saya memperbarui dan menyederhanakan sampel, berikut tautan taman bermainnya juga:
Tempat bermain

Saya menambahkan perpustakaan NPM untuk hal di atas, sehingga Anda dapat bekerja dengannya lebih mudah. Saat ini dalam alfa sampai saya mendapatkan pengujian yang tepat, tetapi akan membantu kalian mencoba menulis HKT.

Tautan Github

Saya telah bermain dengan pendekatan sederhana untuk mensimulasikan HKT dengan menggunakan tipe kondisional untuk mengganti variabel tipe virtual dalam tipe jenuh:

declare const index: unique symbol;

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

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

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

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

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

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

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

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

// ... and an instance

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

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

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

Proyek di sini: https://github.com/pelotom/hkts

Umpan balik diterima!

@pelotom Saya suka keringanan sintaksis pendekatan Anda. Ada dua pendekatan lain yang belum disebutkan di utas ini yang mungkin membangkitkan kreativitas tentang bagaimana solusi saat ini dan masa depan dihasilkan. Keduanya adalah solusi Berorientasi Objek untuk masalah ini.

  1. Bertrand Meyer menjelaskan cara untuk mensimulasikan tipe generik dalam bukunya 1988 "Konstruksi Perangkat Lunak Berorientasi Objek".

Contohnya ada di Eiffel, tetapi terjemahan kasar ke TypeScript terlihat seperti ini:

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

Anda akan melihat mereka bisa menjadi sangat berat karena kebutuhan akan representasi kelas menengah, tetapi dengan bentuk pabrik kelas atau dengan pendekatan TypeScript Mixin ini dapat dikurangi secara signifikan.

Mungkin ada beberapa penerapan untuk # 17588

  1. Pendekatan kedua digunakan saat mensimulasikan Aljabar Objek (dan Pabrik Abstrak):

C<T> diwakili oleh App<t,T> dimana T adalah kelasnya, dan t adalah tag unik yang diasosiasikan dengan C

interface App<C,T> {}

Sampel:

interface IApp<C,T> {}

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

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

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

Anda dapat melihat detail dan justifikasi lebih lanjut dalam makalah berikut di bagian 3.5 "Emulasi Jenis-Konstruktor Polimorfisme":

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

@metaweta , dapatkah Anda mengganti nama masalah ini menjadi Higher kinded types in TypeScript agar dapat dilihat lebih baik dari pencarian google?

Mungkin pengelola repositori kami yang bijaksana dan baik hati (misalnya, @RyanCavanaugh , @DanielRosenwasser ) dapat mengedit judul, jika perubahan seperti itu dianggap layak untuk intervensi mereka?

Saya penasaran untuk mengetahui apa artinya ini telah dipindahkan dari komunitas ke backlog. Apakah saat ini tim inti sedang mempertimbangkan dengan lebih serius atau apakah ini berarti tim telah memutuskan bahwa ini bukan kandidat komunitas yang baik?

Ditemukan: Tonggak sejarah "Komunitas" tampaknya tidak digunakan lagi, dan digantikan dengan "Backlog" , jadi masalah ini mungkin bermigrasi dengan cara yang sama.

Bukan anggota TS, hanya seseorang yang memutuskan untuk mengklik link yang diarahkan ulang.

+1

Ini adalah sesuatu yang baru saja saya coba bangun yang sepertinya kasus yang sangat praktis untuk tipe yang lebih baik.

Saya ingin membuat abstraksi untuk database yang dapat berjalan baik secara sinkron maupun asinkron. Daripada menggunakan callback dan meretasnya, saya ingin menggunakan generik. Inilah yang ingin saya lakukan:

type Identity<T> = T

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

Ini akan sangat kuat!

@ccorcos itu disebut gaya MTL. Anda dapat melihat di https://github.com/gcanti/fp-ts/blob/master/tutorials/mtl.ts untuk contoh fungsional murni dengan fp-ts .

@mlegenhausen Maaf, tapi saya kesulitan mengikuti contoh itu.

Setiap kali saya menggali fp-ts , saya khawatir hal-hal menjadi begitu rumit sehingga menjadi rapuh. Contoh @pelotom terlihat lebih mudah untuk diikuti ...

Ada alasan mengapa ini tidak diadopsi ke TypeScript?

@ccorcos IMHO bahkan ketika saya merekomendasikan contoh dari fp-ts Saya tidak akan merekomendasikan gaya MTL / tanpa tag sama sekali. Anda menambahkan lapisan abstraksi ekstra ke setiap monad yang efektif yang perlu Anda kelola secara manual karena skrip ketikan tidak mampu mendeteksi monad mana yang ingin Anda gunakan dan di sinilah segalanya menjadi rumit. Apa yang saya lihat dari komunitas fp-ts adalah menggunakan satu monad asinkron (saya akan merekomendasikan TaskEither ) dan tetap menggunakannya. Bahkan dalam pengujian, manfaat MTL tidak sebanding dengan kerumitan yang Anda dapatkan dalam kode non-pengujian Anda. hyper-ts berdasarkan fp-ts adalah salah satu contoh library yang baru-baru ini menghentikan dukungan untuk MTL.

Menarik ... hyper-ts terlihat sangat keren ...

Saya datang dengan pengkodean tipe ringan yang lebih tinggi berdasarkan polimorfisme terikat-F: https://github.com/strax/tshkt

Manfaat dari pendekatan ini adalah Anda tidak memerlukan tabel pemeta (tipe bersyarat tunggal atau objek dengan kunci string) untuk mengaitkan konstruktor tipe dengan tipe. Teknik ini juga dapat digunakan untuk menyandikan aplikasi fungsi umum tingkat tipe (pikirkan ReturnType<<T>(value: T) => Array<T>> ).

Ini masih merupakan bukti konsep sehingga umpan balik tentang kelayakan pendekatan ini sangat dihargai!

Saya akan melihat @strax itu , yang terlihat sangat keren!

Sementara itu, inilah contoh konyol yang bisa kita lakukan sekarang:

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

// Boolean

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

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

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

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

// Plumbing

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

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

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

Saya ingin menambahkan indeks De Bruijn, karena itu berarti kita tidak membutuhkan antarmuka lagi, tetapi saya pikir itu memerlukan beberapa matematika tuple dan saya mencoba untuk menghindarinya.

Usul

Orde Tinggi, Lamda, Jenis Referensi

Meneruskan Jenis sebagai Referensi

Sederhananya, tipe referensi atau orde yang lebih tinggi akan memungkinkan seseorang untuk menunda parameter yang diambil oleh sebuah tipe untuk kemudian, atau bahkan menyimpulkan parameter tipe (generik) setelahnya. Tetapi mengapa kita harus peduli?

Jika kita dapat mengirimkan sebuah tipe sebagai referensi, itu berarti kita dapat menunda TypeScript dari mengevaluasi sebuah tipe sampai kita memutuskan untuk melakukannya. Mari kita lihat contoh dunia nyata:

Pratinjau dengan Pipe

Bayangkan Anda sedang mengembangkan tipe generik untuk pipe . Sebagian besar pekerjaan adalah tentang memeriksa bahwa fungsi yang akan disalurkan benar-benar dapat disalurkan, jika tidak, kami akan menimbulkan kesalahan kepada pengguna. Untuk melakukannya, kami akan menggunakan tipe yang dipetakan untuk menyalurkan tipe fungsi seperti yang dilakukan pipe(...) :

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

Sekarang, kita hanya perlu mengulangi ini pada fungsi dengan tipe yang dipetakan:

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

( lihat implementasi lengkapnya )

Sekarang kita dapat menyatukan fungsi dan TypeScript dapat memberi kita peringatan:

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

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

Berhasil! kami mendapat kesalahan yang tepat:

Argumen tipe '(message: object) => boolean' tidak dapat diberikan ke parameter tipe '(arg: string) => boolean'.

Masalah

Tapi ada masalah. Meskipun ini bekerja sangat baik untuk operasi sederhana, Anda akan menemukan bahwa itu gagal sepenuhnya ketika Anda mulai menggunakan generik (template) pada fungsi yang Anda berikan padanya:

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

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

Dalam kedua kasus tersebut, TypeScript kehilangan jejak jenis fungsi.
> Di sinilah jenis order yang lebih tinggi ikut bermain <

Sintaksis

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

Singkatnya, kami secara manual dan dinamis menyimpulkan obat generik dengan * . Nyatanya, menggunakan * menunda evaluasi obat generik. Jadi * memiliki perilaku yang berbeda, tergantung pada konteksnya. Jika * adalah tipe yang:

  • dapat menerima obat generik : Ia mengambil alih obat generiknya / mendapatkan referensi
  • adalah generik itu sendiri : Menunda evaluasi sampai referensinya diketahui. Untuk ini, pohon referensi dibangun dari induk ke bawah. Dengan kata lain, refensi dapat disarangkan. Ini persis seperti yang terjadi di atas dengan Return<*(Fns[Pos<Prev<IterationOf<K & string>>>])> yang ditugaskan ke T . Dalam konteks ini, kita dapat mengatakan bahwa * "melindungi" dari evaluasi langsung.
  • bukan salah satu dari yang di atas : Ia tidak melakukan apa-apa, memutuskan jenis yang sama
type  piped  =  Piper<[
    (a:  string) =>  string,
    <B>(b:  B) =>  B
    <C>(c:  C) =>  C
]>
// [
//     (a:  string) =>  string,
//     (b:  string) =>  string,
//     (c:  string) =>  string
// ]

Jadi TypeScript harus mulai / melanjutkan evaluasi hanya jika generik telah disediakan, dan memblokir evaluasi bila diperlukan (generik tidak lengkap). Saat ini, TS mengevaluasi dalam satu kesempatan dengan mengubah obat generik menjadi tipe unknown . Dengan proposal ini, ketika sesuatu tidak dapat diselesaikan:

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

Detail

* mengambil referensi ke suatu tipe, memungkinkan manipulasi pada generiknya. Jadi, menempatkan karakter pengganti di depan suatu jenis akan mengambil referensi ke sana:

*[type]

Mengambil referensi ke suatu tipe secara otomatis mengaktifkan manipulasi generik:

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

Obat generik hanya dikonsumsi / diatur oleh jenis yang ditargetkan jika memungkinkan. Jadi melakukan ini:

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

Tetapi itu juga bisa diperiksa oleh TypeScript itu sendiri, apakah itu harus menampilkan peringatan atau tidak. Tetapi secara internal, TS tidak boleh melakukan apa pun dari ini.

Saya juga berpikir bahwa itu ide yang baik untuk menggunakan * karena dapat melambangkan penunjuk ke sesuatu (seperti dalam bahasa C / C ++), dan tidak dipinjam oleh TypeScript.

Jenis Pesanan Lebih Tinggi

Sekarang kita telah melihat cara kerjanya dalam bentuk paling dasar, saya ingin memperkenalkan konsep inti: jenis lambda . Alangkah baiknya jika memiliki tipe anonim, mirip dengan callback, lambda, referensi di JavaScript .

Contoh di atas menunjukkan bagaimana mengambil alih generik suatu fungsi. Tetapi karena kita berbicara tentang referensi, tipe apa pun dapat digunakan bersama dengan * . Sederhananya, referensi tipe adalah tipe yang bisa kita bagikan tetapi belum menerima generiknya:

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

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

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

Tipe Jenis Lebih Tinggi

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

Istilah Pencarian

lebih tinggi #order #type #references #lambda #HKTs

@ pirix-gh jika Anda membaca hanya beberapa pesan, Anda dapat melihat bahwa banyak dari yang Anda minta sudah mungkin atau sudah diminta.

Saya membacanya, saya pikir saya bisa merangkum ide-ide saya seperti yang dilakukan orang lain (untuk solusi all-in-one), kebanyakan tentang sintaks.

Saya mengedit proposal di atas untuk penjelasan yang lebih baik tentang bagaimana kita dapat merangkai referensi, dan memperbaiki cara tipe seperti Pipe akan bekerja dengannya (ada beberapa kesalahan terkait logikanya).

Perubahan apapun?

Masih belum ada pembaruan? Menurut pendapat saya, masalah ini menempati peringkat pertama sebagai hambatan untuk TypeScript mencapai potensi penuhnya. Ada begitu banyak contoh di mana saya mencoba mengetik perpustakaan saya dengan benar, hanya untuk menyerah setelah perjuangan yang panjang, menyadari bahwa saya telah melawan batasan ini lagi. Itu menyebar, muncul bahkan dalam skenario yang tampaknya sangat sederhana. Sangat berharap itu akan segera diatasi.

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

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

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

type sn = (tmp: string) => number

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

Banyak pekerjaan yang masih harus diselesaikan, seperti: fix quickinfo, typeParameter infer, add error message, hightlight same typeConstructor .....
Tapi itu mulai berhasil, dan inilah yang bisa saya dapatkan untuk saat ini.
Antarmuka contoh dari @millsp https://github.com/microsoft/TypeScript/issues/1213#issuecomment -523245130, kesimpulannya sangat membantu, terima kasih banyak untuk itu.

Saya berharap komunikasi dapat memberikan lebih banyak kasus pengguna seperti itu, untuk memeriksa apakah cara saat ini berfungsi untuk sebagian besar situasi.

Akan menyenangkan juga untuk memberikan beberapa info tentang HKT / function programming / lambda (ketika saya mengatakan lambda , maksud saya matematika, saya hanya dapat menemukan contoh yang ditulis oleh beberapa bahasa, tanpa matematika)

Inilah hal-hal yang sangat membantu saya:

@ShuiRuTian Mengenai m.join(q) mengembalikan Set<unknown> , saya berasumsi --noImplicitAny menyebabkan itu mengeluarkan peringatan juga?

Saya berharap komunitas dapat menyediakan lebih banyak kasus pengguna seperti itu, untuk memeriksa apakah cara saat ini berfungsi untuk sebagian besar situasi.

Akan menyenangkan juga untuk memberikan beberapa info tentang HKT / function programming / lambda (ketika saya mengatakan lambda , maksud saya matematika, saya hanya dapat menemukan contoh yang ditulis oleh beberapa bahasa, tanpa matematika)

Tanpa melangkah lebih jauh, saya baru-baru ini mencoba membuat fungsi kari generik filter , dan saya ingin itu melakukan sesuatu seperti ini:

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

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

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

Dan saya tidak memiliki cara untuk melakukannya, karena saya membutuhkan cara untuk memberi tahu TypeScript "kembalikan tipe yang sama yang Anda terima, tetapi dengan parameter lain ini". Sesuatu seperti ini:

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

@lukeshiru ya, pada dasarnya ini adalah https://pursuit.purescript.org/packages/purescript-filterable/2.0.1/docs/Data.Filterable#v : filter
Ada banyak kasus penggunaan serupa lainnya untuk HKT di TypeScript.

@isiaheadows Saya mencobanya. Kamu benar.
@lukeshiru dan @raveclassic Terima kasih! Fitur ini tampaknya cukup beresonansi. Saya akan melihat ini setelah membaca https://gcanti.github.io/fp-ts/learning-resources/

Saya terjebak dan saya tidak tahu apa ⸘ solusi⸘ saat ini ...
Saya mencoba menerapkan spesifikasi Chain :

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

Nilai yang mengimplementasikan spesifikasi Chain juga harus mengimplementasikan spesifikasi Apply.

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

Saya melakukan FunctorSimplex yang kemudian diperpanjang dengan FunctorComplex kemudian diperpanjang oleh Apply tetapi sekarang saya ingin memperpanjang Apply sebagai Chain itu rusak ...

Jadi saya membutuhkannya (gambar di bawah dan tautan ke kode):

(Saya perlu memberikan tipe ke ApType sehingga pada baris 12 Apply tidak «hardcode» tetapi generik ... untuk juga mengambil semua tipe yang diperpanjang dari IApply )

Screenshot

Tautan permanen ke cuplikan kode

`` naskah ketikan
jenis ekspor ApType = ( ap: Terapkan <(val: A) => B>, ) => IApply ;

/ * [...] * /

ekspor antarmuka IApply extends FunctorComplex {
/ ** Fantasy-land/ap :: Apply f => f a ~> f (a -> b) -> f b * /
ap: ApType ;
}
``

Dirujuk ke pertanyaan Stack Overflow:

@Luxcium Hingga TS memiliki dukungan untuk jenis yang lebih tinggi, hanya

Sampai TS memiliki dukungan untuk tipe-tipe yang lebih tinggi, hanya emulasinya yang mungkin. Anda mungkin ingin melihat ke sana untuk melihat bagaimana mungkin mencapainya

Banyak tank @kapke Saya mungkin terlalu banyak ke FP hari ini dan karena di Javascript seseorang dapat mengembalikan fungsi dari fungsi kita dapat menulis pseudoFnAdd(15)(27) // 42 Saya ingin mampu, dengan TypeScript, untuk menulis pseudoType<someClassOrConstructor><number> // unknown tapi saya seorang script kiddie, bukan _ ° semacam orang yang belajar lama di universitas atau sesuatu ° _

Informasi dan ceramah (bacaan) ini sangat dihargai ...

Catatan: Saya berbicara bahasa Prancis, dalam bahasa Prancis kata _lecture (s) _ memiliki arti _readings_ dan bukan 'pembicaraan yang marah atau serius yang diberikan kepada seseorang untuk mengkritik perilaku mereka' ...

Mungkin hal berikut yang saya buat sebagai solusi sederhana tanpa PR tidak akan berfungsi untuk semua kasus, tetapi menurut saya perlu disebutkan:

type AGenericType<T> = T[];

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

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

function MONAD(m: Monad<AGenericType<Placeholder>>, f: (s: string) => number) {
  var a = m.map(f); // (v: string[]) => number[]
  var b = m.lift(1); // number[]
  var c = m.join([[2], [3]]); // number[]
}
Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

uber5001 picture uber5001  ·  3Komentar

blendsdk picture blendsdk  ·  3Komentar

manekinekko picture manekinekko  ·  3Komentar

seanzer picture seanzer  ·  3Komentar

siddjain picture siddjain  ·  3Komentar
bleepcoder.com menggunakan informasi GitHub berlisensi publik untuk menyediakan solusi bagi pengembang di seluruh dunia untuk masalah mereka. Kami tidak berafiliasi dengan GitHub, Inc. atau dengan pengembang mana pun yang menggunakan GitHub untuk proyek mereka. Kami tidak meng-host video atau gambar apa pun di server kami. Semua hak milik masing-masing pemiliknya.
Sumber untuk halaman ini: Sumber

Bahasa pemrograman populer
Proyek GitHub populer
Lebih banyak proyek GitHub

© 2024 bleepcoder.com - Contact
Made with in the Dominican Republic.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.