Mengapa Math.Sqrt () adalah fungsi statis?

31

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.

Residuum
sumber
18
Anda menggunakan bahasa apa ini? Apakah 1.sqrt()akan valid
20
Dalam banyak bahasa (misalnya java), ganda adalah primitif (karena alasan kinerja) sehingga mereka tidak memiliki metode
Richard Tingle
45
Jadi tipe numerik harus membengkak dengan setiap fungsi matematika yang mungkin bisa diterapkan padanya?
D Stanley
19
FWIW Saya pikir Sqrt(x)terlihat jauh lebih alami daripada x.Sqrt() Jika itu berarti menambahkan fungsi dengan kelas dalam beberapa bahasa saya setuju dengan itu. Jika itu adalah metode instance maka x.GetSqrt()akan lebih tepat untuk menunjukkan bahwa itu mengembalikan nilai daripada memodifikasi instance.
D Stanley
23
Pertanyaan ini bukan agnostik bahasa dalam bentuknya saat ini. Itulah akar masalahnya.
RisingDarkness

Jawaban:

20

Ini sepenuhnya merupakan pilihan desain bahasa. Ini juga tergantung pada implementasi yang mendasari tipe primitif, dan pertimbangan kinerja karenanya.

.NET hanya memiliki satu Math.Sqrtmetode statis yang bekerja pada doubledan mengembalikan adouble . Apa pun yang Anda lewati harus dilemparkan atau dipromosikan menjadi a double.

double sqrt2 = Math.Sqrt(2d);

Di sisi lain, Anda memiliki Rust yang mengekspos operasi ini sebagai fungsi pada tipe :

let sqrt2 = 2.0f32.sqrt();
let higher = 2.0f32.max(3.0f32);

Tetapi Rust juga memiliki sintaks fungsi panggilan universal (seseorang menyebutkan itu sebelumnya), sehingga Anda dapat memilih apa pun yang Anda suka.

let sqrt2 = f32::sqrt(2.0f32);
let higher = f32::max(2.0f32, 3.0f32);
angelsl
sumber
1
Perlu dicatat bahwa dalam. NET Anda dapat menulis metode ekstensi, jadi jika Anda benar - benar ingin mendapatkan implementasi ini terlihat seperti x.Sqrt(), itu bisa dilakukan. public static class DoubleExtensions { public static double Sqrt( this double self) { return Math.Sqrt(self); } }
Zachary Dow
1
Juga di C # 6 itu bisa saja Sqrt(x) msdn.microsoft.com/en-us/library/sf0df423.aspx .
Den
65

Misalkan kita sedang merancang bahasa baru dan kami ingin Sqrtmenjadi metode contoh. Jadi kami melihat doublekelas dan mulai mendesain. Jelas tidak memiliki input (selain dari instance) dan mengembalikan a double. 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 intdan mulai mendesain. Apa yang dikembalikan? Kami dapat mengembalikan intdan membuatnya hanya berfungsi untuk kuadrat sempurna, atau membulatkan hasilnya ke yang terdekat int(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 mengembalikan intdan yang mengembalikan double(yang tidak mungkin dalam beberapa bahasa tanpa mengubah nama). Jadi kami memutuskan bahwa itu harus mengembalikan a double. Sekarang kami laksanakan. Tetapi implementasinya identik dengan yang kami gunakandouble. Apakah kita menyalin dan menempel? Apakah kita cor contoh ke doubledan memanggil yang metode contoh? Mengapa tidak meletakkan logika dalam metode pustaka yang dapat diakses dari kedua kelas. Kami akan memanggil perpustakaan Mathdan fungsinya Math.Sqrt.

Mengapa Math.Sqrtfungsi statis ?:

  • Karena implementasinya sama terlepas dari jenis numerik yang mendasarinya
  • Karena tidak memengaruhi instance tertentu (ia mengambil satu nilai dan mengembalikan hasil)
  • Karena tipe numerik tidak bergantung pada fungsi itu, maka masuk akal untuk memilikinya dalam kelas yang terpisah

Kami bahkan belum membahas argumen lain:

  • Haruskah ini dinamai GetSqrtkarena mengembalikan nilai baru daripada memodifikasi instance?
  • Bagaimana dengan Square? Abs? Trunc? Log10? Ln? Power? Factorial? Sin? Cos? ArcTan?
D Stanley
sumber
6
Belum lagi kegembiraan 1.sqrt()vs 1.1.sqrt()(ghads, yang terlihat jelek) apakah mereka memiliki kelas dasar yang sama? Apa kontrak untuk sqrt()metodenya?
5
@MichaelT Contoh yang bagus. Butuh empat bacaan untuk memahami apa yang 1.1.Sqrtdiwakili. Pintar.
D Stanley
17
Saya tidak benar-benar jelas dari jawaban ini bagaimana kelas statis membantu dengan alasan utama Anda. Jika Anda memiliki Sqrt ganda (int) dan Sqrt ganda (ganda) pada kelas matematika Anda, Anda memiliki dua opsi: konversi int menjadi ganda, lalu panggil ke versi ganda, atau salin dan tempel metode dengan perubahan yang sesuai (jika ada ). Tetapi itu adalah opsi yang persis sama dengan yang Anda gambarkan untuk versi instance. Alasan Anda yang lain (khususnya poin ketiga Anda) Saya setuju dengan lebih banyak.
Ben Aaronson 3-15
14
-1 jawaban ini tidak masuk akal, apa pun dari ini harus dilakukan dengan menjadi statis? Anda akan memutuskan jawaban untuk pertanyaan-pertanyaan yang sama (dan "[dengan fungsi statis] implementasinya sama" salah, atau setidaknya tidak lebih benar daripada metode contohnya ..)
BlueRaja - Danny Pflughoeft
20
"Implementasinya sama terlepas dari tipe numerik yang mendasarinya" adalah omong kosong. Implementasi fungsi akar kuadrat perlu berbeda secara signifikan berdasarkan jenis yang mereka kerjakan agar tidak menjadi sangat tidak efisien.
R ..
25

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:

  • notasi matematika didasarkan pada operator dan fungsi, bukan pada metode. Ekspresi seperti itu 42.sqrtakan tampak jauh lebih asing bagi banyak pengguna daripada sqrt(42). Sebagai pengguna yang berat, saya lebih suka kemampuan untuk membuat operator saya sendiri daripada sintaks dot-metode-panggilan.
  • Prinsip Tanggung Jawab Tunggal mendorong kita untuk membatasi jumlah operasi yang merupakan bagian dari jenis operasi penting. Dibandingkan dengan multiplikasi, Anda membutuhkan akar kuadrat yang jarang. Jika bahasa Anda secara khusus ditujukan untuk anlysis statistik, kemudian memberikan lebih primitif (seperti operasi mean, median, variance, std, normalizepada 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.
amon
sumber
Python adalah contoh yang baik dari bahasa segalanya-adalah-objek-yang digunakan untuk angka berat. NumPy skalars sebenarnya memiliki banyak metode, tetapi sqrt masih bukan salah satu dari mereka. Sebagian besar dari mereka adalah hal-hal seperti transposedan meanyang hanya ada untuk menyediakan antarmuka yang seragam dengan array NumPy, yang merupakan struktur data pekerja keras yang sebenarnya.
user2357112 mendukung Monica
7
@ user2357112: Masalahnya, NumPy sendiri ditulis dalam campuran C dan Cython, dengan beberapa lem Python. Kalau tidak, itu tidak akan pernah bisa secepat itu.
Kevin
1
Saya pikir jawaban ini sangat dekat dengan semacam kompromi dunia nyata yang telah dicapai selama bertahun-tahun desain. Dalam berita lain, apakah benar-benar masuk akal di .Net untuk dapat melakukan "Hello World". Max () karena ekstensi LINQ memungkinkan kita DAN sangat terlihat di Intellisense. Poin bonus: Apa hasilnya? Bonus Bonus, apa hasilnya di Unicode ...?
Andyz Smith 4-15
15

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 sincukup penting untuk menjadi anggota Double, atau apakah itu di Mathkelas bersama dengan inbrida seperti htandan exp1p?)

Alasan praktis lainnya adalah bahwa mungkin ada cara yang berbeda untuk menerapkan metode numerik, dengan kinerja dan presisi yang berbeda. Java memiliki Math, dan juga StrictMath.

Aleksandr Dubinsky
sumber
Saya berharap perancang bahasa tidak peduli dengan tooltips yang dilengkapi secara otomatis. Juga, apa yang terjadi Math.<^space>? Tooltip lengkapi otomatis itu juga akan tercemar. Sebaliknya, saya pikir paragraf kedua Anda mungkin salah satu jawaban yang lebih baik di sini.
Qix
@Qix Mereka melakukannya. Meskipun, orang lain di sini mungkin menyebutnya "membuat antarmuka kembung."
Aleksandr Dubinsky
6

Anda telah mengamati dengan benar bahwa ada simetri yang aneh di sini.

Apakah saya mengatakan sqrt(n)atau n.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.

KomikSMS
sumber
Python sudah mendukung kedua sintaks ini. Setiap metode non-statis mengambil selfsebagai 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")atau str.startswith("foo", "f"), dan saya dapat menulis my_list.append(x)atau list.append(my_list, x).
Mark Amery
@MarkAmery Poin bagus. Ini tidak se-drastis apa yang dilakukan proposal D atau C ++, tetapi cocok dengan ide umum. Terima kasih telah menunjukkan!
ComicSansMS
3

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.

Falx
sumber
2

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:

  1. Kumpulkan semua fungsi helper menjadi seperti kelas pro-forma Math.
  2. Jadikan itu fungsi statis pada bungkus yang sesuai.
  3. Jadikan ini sebagai fungsi anggota pada bungkus yang sesuai.
  4. Ubah aturan Java.

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 dari intmasing - masing double.

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.

Deduplicator
sumber
1
Pertimbangkan kegembiraan menerapkan variasi pada Math.min()semua jenis pembungkus.
Saya menemukan # 4 tidak meyakinkan. Math.sqrt()dibuat pada saat yang sama dengan Jawa lainnya, jadi ketika keputusan dibuat untuk menempatkan sqrt () Mathtidak ada inersia historis pengguna Java yang "suka seperti itu". Meskipun tidak ada banyak masalah dengan sqrt(), perilaku overloading Math.round()sangat mengerikan. Mampu menggunakan sintaks anggota dengan nilai tipe floatdan doubleakan menghindari masalah itu.
supercat
2

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:

class Int {
    public Int add(Int x) {
      // Do something with the bits
    }
    private List<Boolean> getBits() {
      // ...
    }
}

Beberapa dengan sistem modul:

signature INT = sig
  type int
  val add : int -> int -> int
end

structure Word : INT = struct
  datatype int  = (* ... *)
  fun add x y   = (* Do something with the bits *)
  fun getBits x = (* ... *)
end

Beberapa dengan lingkup leksikal:

(defun getAdder ()
   (let ((getBits (lambda (x) ; ...
         (add     (lambda (x y) ; Do something with the bits
     'add))

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.

Warbo
sumber
Tidak ada yang akan mencegah bahasa mengizinkan metode statis untuk dipanggil baik menggunakan sintaks anggota (seperti dengan metode ekstensi C # atau vb.net) atau dengan variasi urutan anggota (saya ingin melihat sintaks double-dot, jadi untuk memungkinkan keuntungan Intellisense karena hanya dapat membuat daftar fungsi yang cocok untuk argumen utama, tetapi menghindari ambiguitas dengan operator anggota nyata).
supercat
-2

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.

Pieter B
sumber
1
Tentu saja itu adalah keputusan, yaitu keputusan untuk memiliki objectmemiliki ToString()metode. Itu dalam kata-kata Anda "beberapa orang seperti: hei mari kita memiliki metode ToString untuk tipe Nilai".
Residuum
-3

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.

HaLeiVi
sumber
3
Contoh balasan : BigInteger.pow () .