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.
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>
}
Akan berguna di sini juga http://stackoverflow.com/questions/36900619/how-do-i-express-this-in-typescript
: +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:
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._.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
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
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:
(* => *, (*,*) => *) => *
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.
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-d41c361d0dbeSaya 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).
~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.
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.
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 tampaknya masuk akal, tetapi saya mengalami sejumlah kesulitan saat mencoba mencirikannya.
Ide pertama saya adalah sebagai berikut. Untuk ~A
menjadi subtipe dari ~B
:
- (a) Mereka harus memiliki jenis yang sama (dalam istilah arity, bukan kendala).
- (b) Untuk setiap parameterisasi legal
T₁, T₂, ...
dari~A
,A<T₁, T₂, ...>
harus merupakan subtipe dariB<T₁, T₂, ...>
.
Namun, ini memiliki beberapa keterbatasan.
class MySpecialPromise mengimplementasikan PromiseLike {} Dalam kasus ini, ~MySpecialPromise
bukan merupakan subtipe dari ~PromiseLike
karena keduanya memiliki jenis yang berbeda.
kelas MyArrayPromise 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 parameterisasiS₁, S₂, ...
dari~B
sehinggaA<T₁, T₂, ...>
adalah subtipe dariB<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 parameterA<...>
. 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:
~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 PromiseLike dalam 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 dariPromiseLike<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:
((* => *) => *) => *
akan membutuhkan pengenalan banyak tipe eksistensial, beberapa di antaranya harus berorde lebih tinggi. Semuanya harus menjadi ujung tombak dalam tanda tangan fungsi atau kelas.@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
:
- (a) Mereka harus memiliki jenis yang sama (dalam istilah arity, bukan kendala).
- (b) Untuk setiap parameterisasi legal
T₁, T₂, ...
dari~A
,A<T₁, T₂, ...>
harus merupakan subtipe dariB<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.
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
:
- (a) Mereka harus memiliki jenis yang sama (dalam istilah arity, bukan kendala).
- (b) Untuk setiap parameterisasi legal
T₁, T₂, ...
dari~A
,A<T₁, T₂, ...>
harus merupakan subtipe dariB<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.
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:
Promise
pesan kesalahannya akan sama persis seperti sekarang. Tidak perlu berubah untuk membicarakan TC.~\w
muncul di mana tipe diharapkan akan dianggap menunjukkan referensi ke TC.Saya berharap orang lain bisa memberikan pendapatnya.
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.
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
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.
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?
Saya tidak berpikir itu harus segera diimplementasikan, tetapi menurut saya sintaks struktural saya adalah ide yang bagus. Ini memungkinkan Anda:
~<A>Map<string, A>
, ~<A, B>Map<B, A>
, dan seterusnya.Meskipun demikian, TC dapat sepenuhnya bekerja tanpa ini, dan PR pertama mungkin tidak akan melibatkan mereka.
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.
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.
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.
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 MyPromise
memperpanjang 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. : /
Beberapa ide yang relevan di https://github.com/SimonMeskens/TypeProps/issues/1.
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.
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.
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
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.
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:
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'.
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 <
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:
Return<*(Fns[Pos<Prev<IterationOf<K & string>>>])>
yang ditugaskan ke T
. Dalam konteks ini, kita dapat mengatakan bahwa *
"melindungi" dari evaluasi langsung.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
// ]
*
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.
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
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>;
}
@ 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 12Apply
tidak «hardcode» tetapi generik ... untuk juga mengambil semua tipe yang diperpanjang dariIApply
)
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[]
}
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