Dalam sebuah diskusi tentang metode statis dan contoh, saya selalu berpikir, itu Sqrt()
harus menjadi metode contoh tipe angka daripada metode statis. Mengapa demikian? Jelas bekerja pada suatu nilai.
// looks wrong to me
var y = Math.Sqrt(x);
// looks better to me
var y = x.Sqrt();
Tipe nilai jelas dapat memiliki metode instan, seperti dalam banyak bahasa, ada metode instan ToString()
.
Untuk menjawab beberapa pertanyaan dari komentar: Mengapa 1.Sqrt()
tidak legal? 1.ToString()
aku s.
Beberapa bahasa tidak memungkinkan untuk memiliki metode pada tipe nilai, tetapi beberapa bahasa bisa. Saya berbicara tentang ini, termasuk Java, ECMAScript, C # dan Python (dengan yang __str__(self)
ditentukan). Hal yang sama berlaku untuk fungsi lain seperti ceil()
, floor()
dll.
1.sqrt()
akan validSqrt(x)
terlihat jauh lebih alami daripadax.Sqrt()
Jika itu berarti menambahkan fungsi dengan kelas dalam beberapa bahasa saya setuju dengan itu. Jika itu adalah metode instance makax.GetSqrt()
akan lebih tepat untuk menunjukkan bahwa itu mengembalikan nilai daripada memodifikasi instance.Jawaban:
Ini sepenuhnya merupakan pilihan desain bahasa. Ini juga tergantung pada implementasi yang mendasari tipe primitif, dan pertimbangan kinerja karenanya.
.NET hanya memiliki satu
Math.Sqrt
metode statis yang bekerja padadouble
dan mengembalikan adouble
. Apa pun yang Anda lewati harus dilemparkan atau dipromosikan menjadi adouble
.Di sisi lain, Anda memiliki Rust yang mengekspos operasi ini sebagai fungsi pada tipe :
Tetapi Rust juga memiliki sintaks fungsi panggilan universal (seseorang menyebutkan itu sebelumnya), sehingga Anda dapat memilih apa pun yang Anda suka.
sumber
x.Sqrt()
, itu bisa dilakukan.public static class DoubleExtensions { public static double Sqrt( this double self) { return Math.Sqrt(self); } }
Sqrt(x)
msdn.microsoft.com/en-us/library/sf0df423.aspx .Misalkan kita sedang merancang bahasa baru dan kami ingin
Sqrt
menjadi metode contoh. Jadi kami melihatdouble
kelas dan mulai mendesain. Jelas tidak memiliki input (selain dari instance) dan mengembalikan adouble
. Kami menulis dan menguji kodenya. Kesempurnaan.Tetapi mengambil akar kuadrat dari bilangan bulat juga valid, dan kami tidak ingin memaksa semua orang mengonversi menjadi ganda hanya untuk mengambil akar kuadrat. Jadi kami pindah ke
int
dan mulai mendesain. Apa yang dikembalikan? Kami dapat mengembalikanint
dan membuatnya hanya berfungsi untuk kuadrat sempurna, atau membulatkan hasilnya ke yang terdekatint
(mengabaikan perdebatan tentang metode pembulatan yang tepat untuk saat ini). Tetapi bagaimana jika seseorang menginginkan hasil yang bukan bilangan bulat? Haruskah kita memiliki dua metode - satu yang mengembalikanint
dan yang mengembalikandouble
(yang tidak mungkin dalam beberapa bahasa tanpa mengubah nama). Jadi kami memutuskan bahwa itu harus mengembalikan adouble
. Sekarang kami laksanakan. Tetapi implementasinya identik dengan yang kami gunakandouble
. Apakah kita menyalin dan menempel? Apakah kita cor contoh kedouble
dan memanggil yang metode contoh? Mengapa tidak meletakkan logika dalam metode pustaka yang dapat diakses dari kedua kelas. Kami akan memanggil perpustakaanMath
dan fungsinyaMath.Sqrt
.Kami bahkan belum membahas argumen lain:
GetSqrt
karena mengembalikan nilai baru daripada memodifikasi instance?Square
?Abs
?Trunc
?Log10
?Ln
?Power
?Factorial
?Sin
?Cos
?ArcTan
?sumber
1.sqrt()
vs1.1.sqrt()
(ghads, yang terlihat jelek) apakah mereka memiliki kelas dasar yang sama? Apa kontrak untuksqrt()
metodenya?1.1.Sqrt
diwakili. Pintar.Operasi matematika seringkali sangat sensitif terhadap kinerja. Oleh karena itu, kami ingin menggunakan metode statis yang dapat diselesaikan sepenuhnya (dan dioptimalkan, atau digarisbawahi) pada waktu kompilasi. Beberapa bahasa tidak menawarkan mekanisme apa pun untuk menentukan metode yang dikirim secara statis. Selain itu, model objek banyak bahasa memiliki overhead memori yang cukup besar yang tidak dapat diterima untuk jenis "primitif" seperti
double
.Beberapa bahasa memungkinkan kita untuk mendefinisikan fungsi yang menggunakan sintaks pemanggilan metode, tetapi sebenarnya dikirim secara statis. Metode ekstensi dalam C # 3.0 atau lebih baru adalah contohnya. Metode non-virtual (mis. Standar untuk metode dalam C ++) adalah kasus lain, meskipun C ++ tidak mendukung metode pada tipe primitif. Anda tentu saja dapat membuat kelas pembungkus Anda sendiri dalam C ++ yang menghiasi tipe primitif dengan berbagai metode, tanpa overhead runtime. Namun, Anda harus mengonversi nilai secara manual ke jenis pembungkus itu.
Ada beberapa bahasa yang mendefinisikan metode pada tipe numerik mereka. Ini biasanya bahasa yang sangat dinamis di mana semuanya adalah objek. Di sini, kinerja adalah pertimbangan sekunder untuk keanggunan konseptual, tetapi bahasa-bahasa itu umumnya tidak digunakan untuk angka-angka. Namun, bahasa ini mungkin memiliki pengoptimal yang dapat "menghapus kotak" operasi pada primitif.
Dengan tidak adanya pertimbangan teknis, kita dapat mempertimbangkan apakah antarmuka matematika berbasis metode seperti itu akan menjadi antarmuka yang baik. Dua masalah muncul:
42.sqrt
akan tampak jauh lebih asing bagi banyak pengguna daripadasqrt(42)
. Sebagai pengguna yang berat, saya lebih suka kemampuan untuk membuat operator saya sendiri daripada sintaks dot-metode-panggilan.mean
,median
,variance
,std
,normalize
pada daftar numerik, atau fungsi Gamma untuk nomor) dapat berguna. Untuk bahasa tujuan umum, ini hanya membebani antarmuka. Mengalihkan operasi yang tidak penting ke ruang nama yang terpisah membuat tipe ini lebih mudah diakses oleh sebagian besar pengguna.sumber
transpose
danmean
yang hanya ada untuk menyediakan antarmuka yang seragam dengan array NumPy, yang merupakan struktur data pekerja keras yang sebenarnya.Saya akan termotivasi oleh kenyataan bahwa ada satu ton fungsi matematika tujuan khusus, dan bukannya mengisi setiap jenis matematika dengan semua (atau subset acak) dari fungsi-fungsi yang Anda letakkan di kelas utilitas. Jika tidak, Anda akan mencemari tooltip penyelesaian otomatis Anda, atau Anda akan memaksa orang untuk selalu mencari di dua tempat. (Apakah
sin
cukup penting untuk menjadi anggotaDouble
, atau apakah itu diMath
kelas bersama dengan inbrida sepertihtan
danexp1p
?)Alasan praktis lainnya adalah bahwa mungkin ada cara yang berbeda untuk menerapkan metode numerik, dengan kinerja dan presisi yang berbeda. Java memiliki
Math
, dan jugaStrictMath
.sumber
Math.<^space>
? Tooltip lengkapi otomatis itu juga akan tercemar. Sebaliknya, saya pikir paragraf kedua Anda mungkin salah satu jawaban yang lebih baik di sini.Anda telah mengamati dengan benar bahwa ada simetri yang aneh di sini.
Apakah saya mengatakan
sqrt(n)
ataun.sqrt()
tidak terlalu penting, mereka berdua mengekspresikan hal yang sama dan mana yang Anda sukai lebih merupakan masalah selera pribadi daripada hal lainnya.Itulah sebabnya ada argumen kuat dari perancang bahasa tertentu untuk membuat kedua sintaksis itu dapat dipertukarkan. Bahasa pemrograman D sudah memungkinkan ini di bawah fitur yang disebut Uniform Function Call Syntax . Fitur serupa juga telah diusulkan untuk standardisasi dalam C ++ . Seperti yang ditunjukkan Mark Amery dalam komentar , Python juga mengizinkan ini.
Ini bukan tanpa masalah. Memperkenalkan perubahan sintaksis mendasar seperti ini memiliki konsekuensi luas untuk kode yang ada dan tentu saja juga menjadi topik diskusi kontroversial di antara para pengembang yang telah dilatih selama beberapa dekade untuk memikirkan kedua sintaksis tersebut sebagai menggambarkan berbagai hal.
Saya kira hanya waktu yang akan mengatakan apakah penyatuan keduanya layak dalam jangka panjang, tetapi jelas merupakan pertimbangan yang menarik.
sumber
self
sebagai parameter pertama, dan ketika Anda memanggil metode sebagai properti dari instance, bukan sebagai properti kelas, instance akan secara implisit dilewatkan sebagai argumen pertama. Karenanya saya dapat menulis"foo".startswith("f")
ataustr.startswith("foo", "f")
, dan saya dapat menulismy_list.append(x)
ataulist.append(my_list, x)
.Selain jawaban D Stanley, Anda harus memikirkan polimorfisme. Metode seperti Math.Sqrt harus selalu mengembalikan nilai yang sama ke input yang sama. Menjadikan metode ini statis adalah cara yang baik untuk memperjelas hal ini, karena metode statis tidak dapat ditimpa.
Anda menyebutkan metode ToString () -. Di sini Anda mungkin ingin menimpa metode ini, sehingga kelas (sub) diwakili dengan cara lain sebagai String sebagai kelas induknya. Jadi Anda menjadikannya Metode instan.
sumber
Nah, di Jawa ada pembungkus untuk setiap tipe dasar.
Dan tipe dasar bukan tipe kelas, dan tidak memiliki fungsi anggota.
Jadi, Anda memiliki pilihan berikut:
Math
.Mari kita singkirkan opsi 4, karena ... Java adalah Java, dan penganutnya mengaku menyukainya.
Sekarang, kami juga bisa mengesampingkan opsi 3 karena sementara mengalokasikan objek cukup murah, itu tidak bebas, dan melakukan hal itu lagi dan lagi tidak menambahkan.
Dua turun, satu masih untuk membunuh: Opsi 2 juga merupakan ide yang buruk, karena itu berarti setiap fungsi harus diterapkan untuk setiap jenis, seseorang tidak dapat mengandalkan konversi pelebaran untuk mengisi kesenjangan, atau ketidakkonsistenan akan benar-benar menyakitkan.
Dan melihat
java.lang.Math
, ada banyak celah, terutama untuk jenis yang lebih kecil dariint
masing - masingdouble
.Jadi, pada akhirnya pemenang yang jelas adalah pilihan pertama, mengumpulkan mereka semua di satu tempat di kelas fungsi-utilitas.
Kembali ke opsi 4, sesuatu ke arah itu benar-benar terjadi jauh di kemudian hari: Anda dapat meminta kompiler untuk mempertimbangkan semua anggota statis dari kelas apa pun yang Anda inginkan saat menyelesaikan nama untuk waktu yang cukup lama sekarang.
import static someclass.*;
Selain itu, bahasa lain tidak memiliki masalah itu, baik karena mereka tidak memiliki prasangka terhadap fungsi bebas (opsional menggunakan ruang nama) atau jenis kecil yang jauh lebih sedikit.
sumber
Math.min()
semua jenis pembungkus.Math.sqrt()
dibuat pada saat yang sama dengan Jawa lainnya, jadi ketika keputusan dibuat untuk menempatkan sqrt ()Math
tidak ada inersia historis pengguna Java yang "suka seperti itu". Meskipun tidak ada banyak masalah dengansqrt()
, perilaku overloadingMath.round()
sangat mengerikan. Mampu menggunakan sintaks anggota dengan nilai tipefloat
dandouble
akan menghindari masalah itu.Satu hal yang saya tidak lihat disebutkan secara eksplisit (walaupun amon menyinggung itu) adalah bahwa akar kuadrat dapat dianggap sebagai operasi "turunan": jika implementasi tidak menyediakannya untuk kita, kita dapat menulis milik kita sendiri.
Karena pertanyaan ini ditandai dengan desain bahasa, kami mungkin mempertimbangkan beberapa deskripsi agnostik bahasa. Meskipun banyak bahasa memiliki filosofi yang berbeda, sangat umum lintas paradigma untuk menggunakan enkapsulasi untuk melestarikan invarian; yaitu untuk menghindari memiliki nilai yang tidak berperilaku seperti yang disarankan oleh tipenya.
Sebagai contoh, jika kita memiliki beberapa implementasi bilangan bulat menggunakan kata-kata mesin, kita mungkin ingin merangkum representasi entah bagaimana (misalnya untuk mencegah pergeseran bit dari mengubah tanda), tetapi pada saat yang sama kita masih memerlukan akses ke bit tersebut untuk mengimplementasikan operasi seperti tambahan.
Beberapa bahasa dapat menerapkan ini dengan kelas dan metode pribadi:
Beberapa dengan sistem modul:
Beberapa dengan lingkup leksikal:
Dan seterusnya. Namun, tidak satu pun dari mekanisme ini diperlukan untuk mengimplementasikan akar kuadrat: ia dapat diimplementasikan menggunakan antarmuka publik dari tipe numerik, dan karenanya tidak perlu akses ke detail implementasi yang dienkapsulasi.
Oleh karena itu lokasi akar kuadrat datang ke filosofi / selera bahasa, dan perancang perpustakaan. Beberapa mungkin memilih untuk memasukkannya "dalam" nilai-nilai numerik (misalnya membuat metode contoh), beberapa mungkin memilih untuk meletakkannya pada tingkat yang sama seperti operasi primitif (ini mungkin berarti metode contoh, atau mungkin berarti hidup di luar yang nilai numerik, tetapi di dalam modul / kelas / namespace yang sama, misalnya sebagai fungsi mandiri atau metode statis), beberapa mungkin memilih untuk memasukkannya ke dalam koleksi fungsi "pembantu", beberapa mungkin memilih untuk mendelegasikannya ke perpustakaan pihak ketiga.
sumber
Dalam Java dan C # ToString adalah metode objek, akar dari hirarki kelas, sehingga setiap objek akan menerapkan metode ToString. Untuk Integertype adalah wajar bahwa implementasi ToString akan bekerja seperti ini.
Jadi alasan Anda salah. Alasan tipe nilai menerapkan ToString bukan karena beberapa orang menyukai: hei mari kita memiliki metode ToString untuk tipe Nilai. Itu karena ToString sudah ada di sana dan itu "hal yang paling alami" untuk dihasilkan.
sumber
object
memilikiToString()
metode. Itu dalam kata-kata Anda "beberapa orang seperti: hei mari kita memiliki metode ToString untuk tipe Nilai".Tidak seperti String.substring, Number.sqrt bukan benar-benar atribut nomor tetapi hasil baru berdasarkan nomor Anda. Saya pikir meneruskan nomor Anda ke fungsi kuadrat lebih intuitif.
Selain itu, objek Math berisi anggota statis lainnya dan lebih masuk akal untuk menyatukan mereka dan menggunakannya secara seragam.
sumber