Mengapa tidak ada konversi tersirat?

14

Seperti yang saya pahami, konversi implisit dapat menyebabkan kesalahan.

Tapi itu tidak masuk akal - bukankah konversi normal juga menyebabkan kesalahan?

Kenapa tidak

len(100)

bekerja dengan menafsirkan bahasa (atau kompilasi) sebagai

len(str(100))

terutama karena itulah satu - satunya cara (saya tahu) untuk itu bekerja. Bahasa tahu kesalahannya, mengapa tidak memperbaikinya?

Untuk contoh ini saya menggunakan Python, meskipun saya merasa bahwa untuk sesuatu yang sekecil ini pada dasarnya bersifat universal.

Quelklef
sumber
2
perl -e 'print length(100);'cetakan 3.
2
Dan dengan demikian sifat bahasa dan sistem tipenya. Itu adalah bagian dari desain python.
2
Itu tidak memperbaikinya, karena ia tidak tahu tujuan Anda. mungkin Anda ingin melakukan sesuatu yang sama sekali berbeda. seperti menggugat loop tetapi tidak pernah melakukan pemrograman seperti sebelumnya. jadi jika itu memperbaikinya sendiri pengguna tidak tahu apakah dia salah atau bahwa implemantasi tidak akan melakukan apa yang dia harapkan.
Zaibis
5
@PieCrust Bagaimana Python seharusnya mengkonversi? Hal ini tidak benar bahwa semua kemungkinan konversi akan mengembalikan hasil yang sama.
Bakuriu
7
@PieCrust: string dan array adalah iterables. mengapa strcara implisit untuk mengubah int menjadi iterable? bagaimana range? atau bin( hex, oct) atau chr(atau unichr)? Semua itu mengembalikan iterables, meskipun strtampaknya yang paling jelas untuk Anda dalam situasi ini.
njzk2

Jawaban:

37

Untuk apa nilainya len(str(100)),, len(chr(100))dan len(hex(100))semuanya berbeda. stradalah tidak satu-satunya cara untuk membuatnya bekerja, karena ada lebih dari satu konversi yang berbeda dengan Python dari integer ke string. Salah satu dari mereka tentu saja adalah yang paling umum, tetapi tidak perlu berarti bahwa itu yang Anda maksudkan. Konversi tersirat secara harfiah berarti, "tidak perlu dikatakan lagi".

Salah satu masalah praktis dengan konversi implisit adalah bahwa tidak selalu jelas konversi mana yang akan diterapkan di mana ada beberapa kemungkinan, dan ini mengakibatkan pembaca membuat kesalahan dalam menafsirkan kode karena mereka gagal menemukan konversi implisit yang benar. Setiap orang selalu mengatakan bahwa apa yang mereka maksudkan adalah "interpretasi yang jelas". Jelas bagi mereka karena itulah yang mereka maksudkan. Mungkin tidak jelas bagi orang lain.

Inilah sebabnya (sebagian besar waktu) Python lebih memilih eksplisit daripada implisit, ia lebih memilih untuk tidak mengambil risiko. Kasus utama di mana Python melakukan tipe paksaan adalah dalam aritmatika. Hal ini memungkinkan 1 + 1.0karena alternatif akan terlalu mengganggu hidup dengan, tetapi tidak memungkinkan 1 + "1"karena berpikir Anda harus harus menentukan apakah Anda maksud int("1"), float("1"), ord("1"), str(1) + "1", atau sesuatu yang lain. Itu juga tidak memungkinkan (1,2,3) + [4,5,6], meskipun bisa mendefinisikan aturan untuk memilih jenis hasil, sama seperti itu mendefinisikan aturan untuk memilih jenis hasil 1 + 1.0.

Bahasa lain tidak setuju dan memiliki banyak konversi implisit. Semakin banyak mereka memasukkan, semakin tidak jelas mereka menjadi. Coba hafalkan aturan dari standar C untuk "promosi bilangan bulat" dan "konversi aritmatika biasa" sebelum sarapan!

Steve Jessop
sumber
+1 Untuk menunjukkan bagaimana asumsi awal, yang lenhanya dapat bekerja dengan satu jenis, pada dasarnya cacat.
KChaloux
2
@KChaloux: sebenarnya, dalam contoh saya str, chrdan hexlakukan semua mengembalikan tipe yang sama! Saya mencoba untuk memikirkan jenis yang berbeda yang konstruktornya dapat mengambil hanya int, tetapi saya belum menemukan apa pun. Ideal akan menjadi wadah di Xmana len(X(100)) == 100;-) numpy.zerostampaknya agak tidak jelas.
Steve Jessop
2
Untuk contoh masalah yang mungkin terjadi, lihat ini: docs.google.com/document/d/… dan destroyallsoftware.com/talks/wat
Jens Schauder
1
Konversi implisit (saya pikir ini disebut pemaksaan) dapat menyebabkan hal-hal jahat seperti memiliki a == b, b == c, tapi a == ctidak menjadi benar.
bgusach
Jawaban yang sangat bagus. Semua yang ingin saya katakan, hanya kata-kata yang lebih baik! Saya hanya berdalih kecil bahwa promosi integer C cukup sederhana dibandingkan dengan menghafal aturan paksaan JavaScript, atau membungkus kepala Anda di sekitar basis kode C ++ dengan penggunaan konstruktor implisit yang ceroboh.
GrandOpener
28

Seperti yang saya pahami, konversi implisit dapat menyebabkan kesalahan.

Anda kehilangan kata: konversi implisit dapat menyebabkan kesalahan runtime .

Untuk kasus sederhana seperti yang Anda perlihatkan, cukup jelas apa yang Anda maksudkan. Tetapi bahasa tidak bisa digunakan pada kasus. Mereka perlu bekerja dengan aturan. Untuk banyak situasi lain, tidak jelas apakah pemrogram membuat kesalahan (menggunakan jenis yang salah) atau jika pemrogram bermaksud melakukan apa yang diasumsikan oleh kode.

Jika kode tersebut salah, Anda mendapatkan kesalahan runtime. Karena sulit untuk dilacak, banyak bahasa salah arah yang memberitahu Anda bahwa Anda mengacaukan dan membiarkan Anda memberi tahu komputer apa yang sebenarnya Anda maksudkan (perbaiki bug atau lakukan konversi secara eksplisit). Bahasa lain membuat dugaan, karena gaya mereka cocok untuk kode cepat dan mudah yang lebih mudah untuk di-debug.

Satu hal yang perlu diperhatikan adalah bahwa konversi implisit membuat kompiler sedikit lebih kompleks. Anda harus lebih berhati-hati tentang siklus (mari kita coba konversi ini dari A ke B; oops yang tidak berfungsi, tetapi ada konversi dari B ke A! Dan kemudian Anda perlu khawatir tentang siklus ukuran C dan D juga ), yang adalah motivasi kecil untuk menghindari konversi implisit.

Telastyn
sumber
Sebagian besar berbicara tentang fungsi yang hanya dapat bekerja dengan satu jenis, menjadikan konversi jelas. Apa yang akan bekerja dengan banyak jenis, dan jenis yang Anda masukkan membuat perbedaan? print ("295") = print (295), sehingga tidak akan membuat perbedaan, kecuali dengan variabel. Apa yang Anda katakan masuk akal, kecuali untuk paragraf 3d ... Bisakah Anda mengulangi?
Quelklef
30
@PieCrust - 123 + "456", apakah Anda ingin "123456" atau 579? Bahasa pemrograman tidak melakukan konteks, sehingga sulit bagi mereka untuk "mencari tahu", karena mereka perlu mengetahui konteks bahwa penambahan sedang dilakukan. Paragraf mana yang tidak jelas?
Telastyn
1
Juga, mungkin penafsiran sebagai base-10 bukan yang Anda inginkan. Ya, konversi tersirat adalah hal yang baik , tetapi hanya jika mereka tidak bisa menyembunyikan kesalahan.
Deduplicator
13
@PieCrust: Sejujurnya saya tidak akan pernah berharap len(100)untuk kembali 3. Saya akan menemukannya jauh lebih intuitif untuk menghitung jumlah bit (atau byte) dalam representasi 100.
user541686
3
Saya dengan @Mehrdad, dari contoh Anda jelas bahwa Anda pikir itu 100% jelas yang len(100)akan memberi Anda jumlah karakter dari representasi desimal dari angka 100. Tapi itu sebenarnya satu ton asumsi yang Anda buat tanpa disadari dari mereka. Anda bisa membuat argumen yang kuat mengapa jumlah bit atau byte atau karakter dari representasi heksadesimal ( 64-> 2 karakter) harus dikembalikan len().
funkwurm
14

Konversi tersirat sangat mungkin dilakukan. Situasi di mana Anda mendapat masalah adalah ketika Anda tidak tahu ke arah mana sesuatu harus bekerja.

Contohnya dapat dilihat dalam Javascript di mana +operator bekerja dengan cara yang berbeda pada waktu yang berbeda.

>>> 4 + 3
7
>>> "4" + 3
43
>>> 4 + "3"
43

Jika salah satu argumennya adalah string, maka +operatornya adalah rangkaian string, jika tidak maka itu adalah penambahan.

Jika Anda diberi argumen dan tidak tahu apakah itu string atau integer, dan ingin melakukan penambahan dengannya, itu bisa menjadi sedikit berantakan.

Cara lain untuk mengatasinya adalah dari warisan Dasar (yang diikuti oleh perl - lihat Pemrograman adalah Hard, Let's Go Scripting ... )

Dalam Dasar, lenfungsi hanya masuk akal dipanggil pada String (docs untuk visual basic : "Setiap ekspresi String yang valid atau nama variabel. Jika Ekspresi adalah tipe objek, fungsi Len mengembalikan ukuran karena akan ditulis ke file oleh fungsi FilePut. ").

Perl mengikuti konsep konteks ini. Kebingungan yang ada di JavaScript dengan konversi implisit dari jenis untuk +operator yang menjadi kadang-kadang Selain dan kadang-kadang Rangkaian tidak terjadi di perl karena +merupakan selalu penambahan dan .adalah selalu Rangkaian.

Jika sesuatu digunakan dalam konteks skalar, itu skalar (misalnya menggunakan daftar sebagai skalar, daftar berperilaku seolah-olah itu adalah angka yang sesuai dengan panjangnya). Jika Anda menggunakan operator string ( equntuk uji persamaan, cmpuntuk perbandingan string) skalar digunakan seolah-olah itu adalah string. Demikian juga, jika sesuatu digunakan dalam konteks matematika ( ==untuk uji kesetaraan dan <=>perbandingan numerik), skalar digunakan seolah-olah itu angka.

Aturan dasar untuk semua pemrograman adalah "lakukan hal yang paling mengejutkan orang". Ini tidak berarti tidak ada kejutan di sana, tetapi usahanya adalah untuk mengejutkan orang itu.

Pergi ke sepupu dekat perl - php, ada situasi di mana operator dapat bertindak pada sesuatu baik dalam konteks string atau numerik dan perilaku dapat mengejutkan orang. The ++operator adalah salah satu contohnya. Pada angka, ia berperilaku tepat seperti yang diharapkan. Saat bertindak pada string, seperti "aa", itu menambah string ( $foo = "aa"; $foo++; echo $foo;cetakan ab). Itu juga akan berguling sehingga azketika bertambah menjadi ba. Ini belum terlalu mengejutkan.

$foo = "3d8";
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";

( ideone )

Ini mencetak:

3d8
3d9
3e0
4

Selamat datang di bahaya konversi tersirat dan operator yang bertindak berbeda pada string yang sama. (Perl menangani blok kode itu sedikit berbeda - ia memutuskan bahwa "3d8"ketika ++operator diterapkan adalah nilai numerik dari awal dan 4langsung pergi ( ideone ) - perilaku ini dijelaskan dengan baik dalam perlop: Auto-increment dan Auto-decrement )

Sekarang, mengapa satu bahasa melakukan sesuatu dengan satu cara dan yang lain melakukannya dengan cara lain sampai pada pemikiran desain para desainer. Filosofi Perl adalah Ada lebih dari satu cara untuk melakukannya - dan saya dapat memikirkan sejumlah cara untuk melakukan beberapa operasi ini. Di sisi lain, Python memiliki filosofi yang dijelaskan dalam PEP 20 - The Zen of Python yang menyatakan (antara lain): "Harus ada satu - dan lebih disukai hanya satu - cara yang jelas untuk melakukannya."

Perbedaan desain ini mengarah ke berbagai bahasa. Ada satu cara untuk mendapatkan panjang angka dalam Python. Konversi implisit bertentangan dengan filosofi ini.

Bacaan terkait: Mengapa Ruby tidak memiliki konversi implisit Fixnum menjadi String?

Komunitas
sumber
IMHO, bahasa / kerangka kerja yang baik harus sering memiliki banyak cara untuk melakukan hal-hal yang menangani kasus sudut umum berbeda (lebih baik untuk menangani kasus sudut umum sekali dalam bahasa atau kerangka kerja daripada 1000 kali dalam 1000 program); fakta bahwa dua operator melakukan hal yang sama sebagian besar waktu tidak boleh dianggap sebagai hal yang buruk, jika ketika perilaku mereka berbeda akan ada manfaat untuk setiap variasi. Seharusnya tidak sulit untuk membandingkan dua variabel numerik xdan ysedemikian rupa untuk menghasilkan hubungan ekuivalensi atau peringkat, tetapi operator perbandingan IIRC Python ...
supercat
... tidak mengimplementasikan hubungan ekivalensi atau peringkat, dan Python tidak menyediakan operator yang nyaman.
supercat
12

ColdFusion melakukan sebagian besar ini. Itu mendefinisikan seperangkat aturan untuk menangani konversi implisit Anda, memiliki tipe variabel tunggal, dan begitulah.

Hasilnya adalah total anarki, di mana menambahkan "4a" ke 6 adalah 6.16667.

Mengapa? Ya, karena yang pertama dari dua variabel adalah angka, jadi hasilnya akan berupa angka. "4a" diuraikan sebagai tanggal, dan terlihat sebagai "4 pagi". 4 pagi adalah 4: 00/24: 00, atau 1/6 hari (0,16667). Tambahkan ke 6 dan Anda mendapatkan 6.16667.

Daftar memiliki karakter pemisah default koma, jadi jika Anda pernah menambahkan item ke daftar yang mengandung koma, maka Anda baru saja menambahkan dua item. Juga, daftar adalah string yang diam-diam, sehingga mereka dapat diurai sebagai tanggal jika mereka hanya berisi 1 item.

Pemeriksaan perbandingan string jika kedua string dapat diuraikan ke tanggal terlebih dahulu. Karena tidak ada tipe tanggal, ia melakukannya. Hal yang sama untuk angka dan string yang berisi angka, notasi oktal, dan boolean ("benar" dan "ya", dll.)

Daripada gagal cepat dan melaporkan kesalahan, Alih-alih ColdFusion akan menangani konversi tipe data untuk Anda. Dan tidak dengan cara yang baik.

Tentu saja, Anda dapat memperbaiki konversi implisit dengan memanggil fungsi eksplisit seperti DateCompare ... tetapi kemudian Anda telah kehilangan "manfaat" dari konversi implisit.


Untuk ColdFusion, ini adalah cara untuk membantu memberdayakan pengembang. Terutama ketika semua pengembang dulu adalah HTML (ColdFusion bekerja dengan tag, itu disebut CFML, kemudian mereka menambahkan skrip <cfscript>juga, di mana Anda tidak perlu menambahkan tag untuk semuanya). Dan itu bekerja dengan baik ketika Anda hanya ingin sesuatu bekerja. Tetapi ketika Anda membutuhkan hal-hal yang terjadi dengan cara yang lebih tepat, Anda memerlukan bahasa yang menolak untuk melakukan konversi implisit untuk apa pun yang tampaknya bisa menjadi kesalahan.

Pimgd
sumber
1
wow, "total anarki" memang!
hoosierEE
7

Anda mengatakan bahwa konversi tersirat bisa menjadi ide bagus untuk operasi yang tidak ambigu, seperti int a = 100; len(a), di mana Anda jelas bermaksud untuk mengubah int menjadi string sebelum menelepon.

Tapi Anda lupa bahwa panggilan ini mungkin secara sintaksis tidak ambigu, tetapi mereka mungkin mewakili kesalahan ketik yang dibuat oleh programmer yang bermaksud untuk lulus a1, yang merupakan string. Ini adalah contoh yang dibuat-buat, tetapi dengan IDE menyediakan pelengkapan otomatis untuk nama variabel, kesalahan ini terjadi.

Sistem tipe bertujuan untuk membantu kami menghindari kesalahan, dan untuk bahasa yang memilih pemeriksaan tipe yang lebih ketat, konversi implisit akan melemahkannya.

Lihat kesengsaraan Javascript dengan semua konversi implisit yang dilakukan oleh ==, sedemikian rupa sehingga banyak orang sekarang merekomendasikan untuk tetap menggunakan operator =-implisit-konversi ===.

Avner Shahar-Kashtan
sumber
6

Pertimbangkan sejenak konteks pernyataan Anda. Anda mengatakan ini adalah "satu-satunya cara" itu bisa berhasil, tetapi apakah Anda benar-benar yakin itu akan berhasil seperti itu? Bagaimana dengan ini:

def approx_log_10(s):
    return len(s)
print approx_log_10(3.5)  # "3" is probably not what I'm expecting here...

Seperti yang disebutkan orang lain, dalam contoh Anda yang sangat spesifik, tampaknya sederhana bagi kompiler untuk memahami apa yang Anda maksudkan. Tetapi dalam konteks yang lebih besar dari program yang lebih rumit, seringkali sama sekali tidak jelas apakah Anda bermaksud memasukkan string, atau salah ketik satu nama variabel, atau disebut fungsi yang salah, atau salah satu dari banyak kesalahan logika potensial lainnya. .

Dalam kasus di mana penerjemah / kompiler menebak apa yang Anda maksud, kadang-kadang itu berfungsi secara ajaib, dan di lain waktu Anda menghabiskan berjam-jam men-debug sesuatu yang secara misterius tidak bekerja tanpa alasan yang jelas. Dalam kasus rata-rata, jauh lebih aman untuk diberi tahu bahwa pernyataan itu tidak masuk akal, dan luangkan beberapa detik untuk memperbaikinya sesuai dengan maksud Anda sebenarnya.

GrandPener
sumber
3

Konversi tersirat dapat menyulitkan untuk diajak bekerja sama. Di PowerShell:

$a = $(dir *.xml) # typeof a is a list, because there are two XML files in the folder.
$a = $(dir *.xml) # typeof a is a string, because there is one XML file in the folder.

Tiba-tiba ada dua kali lebih banyak pengujian yang diperlukan dan dua kali lebih banyak bug daripada tanpa konversi implisit.

Esben Skov Pedersen
sumber
2

Gips yang eksplisit penting untuk menjelaskan maksud Anda. Pertama-tama menggunakan gips eksplisit menceritakan kisah kepada seseorang yang membaca kode Anda. Mereka mengungkapkan bahwa Anda sengaja melakukan apa yang Anda lakukan. Apalagi hal yang sama berlaku untuk kompiler. Berikut ini adalah ilegal di C # misalnya

double d = 3.1415926;
// code elided
int i = d;

Para pemain akan membuat Anda kehilangan presisi, yang bisa dengan mudah menjadi bug. Oleh karena itu kompiler menolak untuk dikompilasi. Dengan menggunakan pemeran eksplisit Anda memberi tahu kompiler: "Hei, saya tahu apa yang saya lakukan." Dan dia akan pergi: "Oke!" Dan kompilasi.

Paul Kertscher
sumber
1
Sebenarnya, saya tidak suka pemain yang paling eksplisit lebih dari yang implisit; IMHO, satu-satunya hal yang (X)yseharusnya berarti adalah "Saya pikir Y dapat dikonversi ke X; lakukan konversi jika memungkinkan, atau berikan pengecualian jika tidak"; jika konversi berhasil, orang harus dapat memprediksi nilai apa yang dihasilkan * tanpa harus tahu aturan khusus tentang konversi dari tipe yke tipe X. Jika diminta untuk mengkonversi -1.5 dan 1.5 ke integer, beberapa bahasa akan menghasilkan (-2,1); beberapa (-2,2), beberapa (-1,1), dan beberapa (-1,2). Sementara aturan C hampir tidak jelas ...
supercat
1
... konversi semacam itu tampaknya akan lebih tepat dilakukan melalui metode daripada melalui pemeran (terlalu buruk. NET tidak mencakup fungsi putaran-dan-konversi yang efisien, dan Java kelebihan beban dengan cara yang agak konyol).
supercat
Anda mengatakan kepada kompiler " Saya rasa saya tahu apa yang saya lakukan".
gnasher729
@ gnasher729 Ada beberapa kebenaran di dalamnya :)
Paul Kertscher
1

Bahasa yang mendukung konversi / gips implisit, atau pengetikan yang lemah seperti yang kadang-kadang disebut, akan membuat asumsi untuk Anda yang tidak selalu cocok dengan perilaku yang Anda maksudkan atau harapkan. Perancang bahasa mungkin memiliki proses pemikiran yang sama seperti yang Anda lakukan untuk jenis konversi atau serangkaian konversi tertentu, dan dalam hal ini Anda tidak akan pernah melihat masalah; namun jika tidak, maka program Anda akan gagal.

Namun kegagalannya, mungkin atau mungkin tidak jelas. Karena gips implisit ini terjadi pada saat runtime, program Anda akan berjalan dengan senang dan Anda hanya akan melihat masalah ketika Anda melihat output atau hasil dari program Anda. Bahasa yang membutuhkan gips eksplisit (beberapa menyebut ini sebagai pengetikan yang kuat) akan memberi Anda kesalahan sebelum program Anda bahkan memulai eksekusi, yang merupakan masalah yang jauh lebih jelas dan lebih mudah untuk memperbaikinya bahwa gips implisit salah.

Suatu hari seorang teman saya bertanya pada anaknya yang berumur 2 tahun apakah 20 + 20 dan dia menjawab tahun 2020. Saya memberi tahu teman saya, "Dia akan menjadi seorang programmer Javascript".

Dalam Javascript:

20+20
40

20+"20"
"2020"

Dengan demikian Anda dapat melihat masalah yang dapat dibuat oleh pemeran implisit dan mengapa itu bukan sesuatu yang bisa diperbaiki. Cara memperbaikinya saya berpendapat, adalah dengan menggunakan gips eksplisit.

Fred Thomsen
sumber