JavaScript: The Big Whole Well Why


Belum lama ini, JavaScript memiliki tipe data BigInt primitif baru untuk bekerja dengan angka presisi acak. Informasi minimum yang diperlukan telah diberitahukan / diterjemahkan tentang motivasi dan kasus penggunaan. Dan saya ingin memberi sedikit lebih banyak perhatian pada "penjelas" lokal yang berlebihan dalam casting tipe dan TypeError yang tidak terduga. Akankah kita memarahi atau memahami dan memaafkan (lagi)?

Tersirat menjadi eksplisit?


Dalam bahasa di mana konversi tipe implisit telah lama digunakan, itu telah menjadi meme dari hampir semua konferensi dan hanya sedikit orang yang terkejut dengan kerumitan seperti:

1 + {}; // '1[object Object]' 1 + [[0]]; // '10' 1 + new Date; // '1Fri Feb 08 2019 00:32:57 GMT+0300 (,  )' 1 - new Date; // -1549616425060 ... 

Kami tiba-tiba mendapatkan TypeError, mencoba menambahkan dua ANGKA yang tampaknya:

 1 + 1n; // TypeError: Cannot mix BigInt and other types, use explicit conversions 

Dan jika pengalaman sebelumnya tentang hal-hal tersirat tidak mengarah pada gangguan dalam belajar bahasa, maka ada kesempatan kedua untuk memecah dan membuang buku teks ECMA dan pergi ke beberapa Jawa.

Selanjutnya, bahasa ini terus “troll” pengembang js:

 1n + '1'; // '11' 

Oh ya, jangan lupa tentang operator + unary:

 +1n; // TypeError: Cannot convert a BigInt value to a number Number(1n); // 1 

Singkatnya, kita tidak dapat mencampur BigInt dan Number dalam operasi. Akibatnya, tidak disarankan untuk menggunakan "bilangan bulat besar" jika 2 ^ 53-1 ( MAX_SAFE_INTEGER ) cukup untuk tujuan kita.

Keputusan kunci


Ya, ini adalah keputusan utama dari inovasi ini. Jika Anda lupa bahwa ini adalah JavaScript, maka semuanya sangat logis: konversi implisit ini berkontribusi pada hilangnya informasi.

Ketika kami menambahkan dua nilai dari tipe numerik yang berbeda (bilangan bulat besar dan angka floating-point), nilai matematika dari hasilnya mungkin di luar kisaran nilai yang mungkin. Misalnya, nilai ekspresi (2n ** 53n + 1n) + 0,5 tidak dapat secara akurat diwakili oleh salah satu dari jenis ini. Ini bukan lagi bilangan bulat, melainkan bilangan real, tetapi akurasinya tidak lagi dijamin oleh format float64 :

 2n ** 53n + 1n; // 9007199254740993n Number(2n ** 53n + 1n) + 0.5; // 9007199254740992 

Dalam sebagian besar bahasa dinamis, di mana jenis bilangan bulat dan float diwakili, yang pertama ditulis sebagai 1 , dan yang terakhir ditulis sebagai 1.0 . Jadi, selama operasi aritmatika dengan adanya pemisah desimal dalam operan, kita dapat menyimpulkan bahwa akurasi float dalam perhitungan dapat diterima. Tetapi JavaScript bukan salah satunya, dan 1 adalah pelampung! Dan ini berarti komputasi 2n ** 53n + 1 akan menghasilkan float 2 ^ 53. Yang, pada gilirannya, merusak fungsi utama BigInt :

 2 ** 53 === 2 ** 53 + 1; // true 

Yah, tidak ada alasan untuk membicarakan implementasi "menara numerik" juga, karena Anda tidak akan berhasil mengambil nomor yang ada sebagai tipe data numerik umum (untuk alasan yang sama).

Dan untuk menghindari masalah ini, para pemain implisit antara Number dan BigInt dalam operasi dilarang. Akibatnya, "bilangan bulat besar" tidak dapat dengan aman dilemparkan ke fungsi JavaScript atau API Web apa pun, di mana angka yang biasa diharapkan:

 Math.max(1n, 10n); // TypeError 

Anda harus secara eksplisit memilih salah satu dari dua jenis dengan menggunakan Number () atau BigInt () .

Selain itu, untuk operasi dengan tipe campuran, ada penjelasan tentang implementasi yang kompleks atau kehilangan kinerja, yang cukup umum untuk inovasi bahasa kompromi.

Tentu saja, ini berlaku untuk konversi numerik implisit dengan primitif lainnya:

 1 + true; // 2 1n + true; // TypeError 1 + null; // 1 1n + null; // TypeError 

Tetapi rangkaian berikut (sudah) akan berfungsi, karena hasil yang diharapkan adalah string:

 1n + [0]; // '10' 1n + {}; // '1[object Object]' 1n + (_ => 1); // '1_ => 1' 

Pengecualian lain adalah dalam bentuk operator perbandingan (seperti < , > dan == ) antara Number dan BigInt . Juga tidak ada kehilangan keakuratan, karena hasilnya adalah Boolean.

Nah, jika Anda mengingat tipe data Simbol baru sebelumnya, apakah TypeError tidak lagi tampak seperti penambahan yang radikal?

 Symbol() + 1; // TypeError: Cannot convert a Symbol value to a number 

Dan ya, tapi tidak. Memang, secara konseptual simbol bukanlah angka sama sekali, tetapi keseluruhan - sangat banyak:

  1. Sangat tidak mungkin bahwa simbol akan jatuh ke dalam situasi seperti itu. Namun, ini sangat mencurigakan dan TypeError cukup sesuai di sini.
  2. Sangat mungkin dan biasa bahwa "keseluruhan besar" dalam operasi akan berubah menjadi salah satu operan ketika benar-benar tidak ada yang salah.

Operator unary + melempar pengecualian karena masalah kompatibilitas dengan asm.js , di mana Number diharapkan. Plus unary tidak dapat bekerja dengan BigInt dengan cara yang sama seperti Nomor , karena dalam kasus ini kode asm.js sebelumnya akan menjadi ambigu.

Tawaran alternatif


Meskipun relatif sederhana dan "bersih" dari implementasi BigInt , Axel Rauschmeyer menekankan kurangnya inovasi. Yakni, satu-satunya kompatibilitas mundur sebagian dengan Nomor yang ada dan yang berikutnya:
Gunakan Angka hingga int 53-bit. Gunakan Integer jika Anda membutuhkan lebih banyak bit
Sebagai alternatif, ia mengusulkan yang berikut ini .

Biarkan Number menjadi supertype untuk Int dan Double baru :

  • typeof 123.0 === 'number' , dan Number.isDouble (123.0) === true
  • typeof 123 === 'number' , dan Number.isInt (123) === true

Dengan fungsi baru untuk konversi Number.asInt () dan Number.asDouble () . Dan, tentu saja, dengan kelebihan operator dan gips yang diperlukan:

  • Int × Double = Ganda (gips)
  • Ganda × Int = Ganda (dengan gips)
  • Ganda × Ganda = Ganda
  • Int × Int = Int (semua operator kecuali divisi)

Menariknya, dalam versi yang disederhanakan, kalimat ini mengelola (pada awalnya) tanpa menambahkan tipe baru ke bahasa. Sebagai gantinya, definisi The Number Type meluas: di samping semua kemungkinan angka presisi ganda 64-bit (IEEE 754-2008), angka sekarang termasuk semua bilangan bulat. Akibatnya, "angka yang tidak akurat" 123.0 dan "angka pastinya" 123 adalah angka yang terpisah dari tipe Angka tunggal.

Itu terlihat sangat akrab dan masuk akal. Namun, ini merupakan peningkatan serius dari jumlah yang ada, yang lebih cenderung untuk “menghancurkan web” dan alat-alatnya:

  • Ada perbedaan antara 1 dan 1.0 , yang sebelumnya tidak ada. Kode yang ada menggunakannya secara bergantian, yang setelah upgrade dapat menyebabkan kebingungan (tidak seperti bahasa di mana perbedaan ini ada pada awalnya).
  • Ada efek ketika 1 === 1.0 (seharusnya merupakan peningkatan), dan pada saat yang sama, Number.isDouble (1)! == Number.isDouble (1.0) : sekali lagi, ini seperti itu.
  • "Keunikan" kesetaraan 2 ^ 53 dan 2 ^ 53 + 1 menghilang, yang akan mematahkan kode yang bergantung padanya.
  • Masalah kompatibilitas yang sama dengan asm.js dan banyak lagi.

Oleh karena itu, pada akhirnya, kami memiliki solusi kompromi dalam bentuk tipe data baru yang terpisah. Perlu ditekankan bahwa opsi lain juga dipertimbangkan dan dibahas .

Ketika Anda duduk di dua kursi


Sebenarnya, komentar komite dimulai dengan kata-kata:
Temukan keseimbangan antara menjaga intuisi pengguna dan menjaga presisi

Di satu sisi, saya akhirnya ingin menambahkan sesuatu yang "tepat" ke bahasa. Dan di sisi lain, untuk mempertahankan perilaku yang sudah akrab bagi banyak pengembang.

Hanya saja Anda tidak akan dapat menambahkan ini "tepat", karena Anda tidak dapat memecahkannya: matematika, ergonomi bahasa, asm.js, kemungkinan perluasan lebih lanjut dari sistem jenis , produktivitas dan, pada akhirnya, web itu sendiri! Dan Anda tidak dapat memecahkan semuanya pada saat yang sama, yang mengarah ke hal yang sama.

Dan Anda tidak dapat menghentikan intuisi pengguna bahasa, yang tentu saja juga diperdebatkan dengan panas . Benar, apakah berhasil?

Source: https://habr.com/ru/post/id439402/


All Articles