Apakah ada pola desain yang hanya mungkin dalam bahasa yang diketik secara dinamis seperti Python?

30

Saya telah membaca pertanyaan terkait Apakah ada pola desain yang tidak perlu dalam bahasa dinamis seperti Python? dan ingat kutipan ini di Wikiquote.org

Hal yang menakjubkan tentang pengetikan dinamis adalah Anda dapat mengekspresikan apa pun yang dapat dikomputasi. Dan sistem tipe jangan - sistem tipe biasanya decidable, dan mereka membatasi Anda untuk subset. Orang-orang yang menyukai sistem tipe statis berkata “tidak apa-apa, itu cukup baik; semua program menarik yang ingin Anda tulis akan berfungsi sebagai tipe ”. Tapi itu konyol - sekali Anda memiliki sistem tipe, Anda bahkan tidak tahu program apa yang menarik.

--- Rekayasa Perangkat Lunak Radio Episode 140: Newspeak dan Jenis Pluggable dengan Gilad Bracha

Saya bertanya-tanya, adakah pola atau strategi desain yang berguna yang, dengan menggunakan rumusan kutipan, "tidak berfungsi sebagai tipe"?

pengguna7610
sumber
3
Saya mendapati pengiriman ganda dan pola Pengunjung sangat sulit dilakukan dalam bahasa yang diketik secara statis, tetapi mudah dicapai dalam bahasa dinamis. Lihat jawaban ini (dan pertanyaannya) misalnya: programmers.stackexchange.com/a/288153/122079
user3002473
7
Tentu saja. Pola apa pun yang melibatkan pembuatan kelas baru saat runtime, misalnya. (itu juga mungkin di Jawa, tetapi tidak di C ++; ada skala geser dinamika).
user253751
1
Ini akan sangat tergantung pada seberapa canggih sistem tipe Anda :-) Bahasa fungsional biasanya cukup baik dalam hal ini.
Bergi
1
Semua orang tampaknya berbicara sistem tipe seperti Java dan C # daripada Haskell atau OCaml. Bahasa dengan sistem tipe yang kuat bisa sama ringkasnya dengan bahasa yang dinamis tetapi tetap menjaga keamanan jenisnya.
Andrew mengatakan Reinstate Monica
@immibis Itu tidak benar. Sistem tipe statis benar-benar dapat membuat kelas baru, "dinamis" pada saat dijalankan. Lihat Bab 33 dari Yayasan Praktis untuk Bahasa Pemrograman.
gardenhead

Jawaban:

4

Jenis kelas satu

Mengetik dinamis berarti Anda memiliki tipe kelas satu: Anda dapat memeriksa, membuat, dan menyimpan tipe saat runtime, termasuk tipe bahasa itu sendiri. Ini juga berarti bahwa nilai diketik, bukan variabel .

Bahasa yang diketik secara statis dapat menghasilkan kode yang bergantung pada tipe dinamis juga, seperti metode pengiriman, kelas ketik, dll. Tetapi dengan cara yang umumnya tidak terlihat oleh runtime. Paling-paling, mereka memberi Anda beberapa cara untuk melakukan introspeksi. Atau, Anda bisa mensimulasikan tipe sebagai nilai tetapi kemudian Anda memiliki sistem tipe dinamis ad-hoc.

Namun, sistem tipe dinamis jarang hanya memiliki tipe kelas satu. Anda dapat memiliki simbol kelas satu, paket kelas satu, kelas satu .... semuanya. Ini berbeda dengan pemisahan ketat antara bahasa kompiler dan bahasa runtime dalam bahasa yang diketik secara statis. Apa yang dapat dilakukan oleh kompiler atau juru bahasa yang bisa dilakukan runtime juga.

Sekarang, mari kita sepakat bahwa inferensi jenis adalah hal yang baik dan saya ingin kode saya diperiksa sebelum menjalankannya. Namun, saya juga suka bisa menghasilkan dan mengkompilasi kode saat runtime. Dan saya juga suka melakukan precompute pada waktu kompilasi. Dalam bahasa yang diketik secara dinamis, ini dilakukan dengan bahasa yang sama. Di OCaml, Anda memiliki sistem jenis modul / functor, yang berbeda dari sistem jenis utama, yang berbeda dari bahasa preprosesor. Di C ++, Anda memiliki bahasa templat yang tidak ada hubungannya dengan bahasa utama, yang umumnya tidak mengetahui jenis selama eksekusi. Dan itu baik - baik saja dalam bahasa itu, karena mereka tidak ingin memberikan lebih banyak.

Pada akhirnya, itu tidak mengubah benar-benar apa jenis perangkat lunak dapat Anda mengembangkan, tapi ekspresivitas perubahan bagaimana Anda mengembangkan mereka dan apakah sulit atau tidak.

Pola

Pola yang bergantung pada tipe dinamis adalah pola yang melibatkan lingkungan dinamis: kelas terbuka, pengiriman, basis data objek dalam memori, serialisasi, dll. Hal-hal sederhana seperti wadah umum berfungsi karena vektor tidak lupa saat runtime tentang jenis objek yang dipegangnya. (tidak perlu untuk tipe parametrik).

Saya mencoba memperkenalkan banyak cara kode dievaluasi dalam Common Lisp serta contoh-contoh analisis statis yang mungkin (ini adalah SBCL). Contoh sandbox mengkompilasi sebagian kecil kode Lisp yang diambil dari file terpisah. Agar cukup aman, saya mengubah readtable, hanya mengizinkan subset dari simbol standar dan membungkus hal-hal dengan batas waktu.

;;
;; Fetching systems, installing them, etc. 
;; ASDF and QL provide provide resp. a Make-like facility 
;; and system management inside the runtime: those are
;; not distinct programs.
;; Reflexivity allows to develop dedicated tools: for example,
;; being able to find the transitive reduction of dependencies
;; to parallelize builds. 
;; https://gitlab.common-lisp.net/xcvb/asdf-dependency-grovel
;;
(ql:quickload 'trivial-timeout)

;;
;; Readtables are part of the runtime.
;; See also NAMED-READTABLES.
;;
(defparameter *safe-readtable* (copy-readtable *readtable*))
(set-macro-character #\# nil t *safe-readtable*)
(set-macro-character #\: (lambda (&rest args)
                           (declare (ignore args))
                           (error "Colon character disabled."))
                     nil
                     *safe-readtable*)

;; eval-when is necessary when compiling the whole file.
;; This makes the result of the form available in the compile-time
;; environment. 
(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar +WHITELISTED-LISP-SYMBOLS+ 
    '(+ - * / lambda labels mod rem expt round 
      truncate floor ceiling values multiple-value-bind)))

;;
;; Read-time evaluation #.+WHITELISTED-LISP-SYMBOLS+
;; The same language is used to control the reader.
;;
(defpackage :sandbox
  (:import-from
   :common-lisp . #.+WHITELISTED-LISP-SYMBOLS+)
  (:export . #.+WHITELISTED-LISP-SYMBOLS+))

(declaim (inline read-sandbox))

(defun read-sandbox (stream &key (timeout 3))
  (declare (type (integer 0 10) timeout))
  (trivial-timeout:with-timeout (timeout)
    (let ((*read-eval* nil)
          (*readtable* *safe-readtable*)
          ;;
          ;; Packages are first-class: no possible name collision.
          ;;
          (package (make-package (gensym "SANDBOX") :use '(:sandbox))))
      (unwind-protect
           (let ((*package* package))
             (loop
                with stop = (gensym)
                for read = (read stream nil stop)
                until (eq read stop)
                ;;
                ;; Eval at runtime
                ;;
                for value = (eval read)
                ;;
                ;; Type checking
                ;;
                unless (functionp value)
                do (error "Not a function")
                ;; 
                ;; Compile at run-time
                ;;
                collect (compile nil value)))
        (delete-package package)))))

;;
;; Static type checking.
;; warning: Constant 50 conflicts with its asserted type (MOD 11)
;;
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in :timeout 50)))

;; get it right, this time
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in)))

#| /tmp/plugin.lisp
(lambda (x) (+ (* 3 x) 100))
(lambda (a b c) (* a b))
|#

(read-sandbox-file #P"/tmp/plugin.lisp")

;; 
;; caught COMMON-LISP:STYLE-WARNING:
;;   The variable C is defined but never used.
;;

(#<FUNCTION (LAMBDA (#:X)) {10068B008B}>
 #<FUNCTION (LAMBDA (#:A #:B #:C)) {10068D484B}>)

Tidak ada yang di atas "tidak mungkin" dilakukan dengan bahasa lain. Pendekatan plug-in di Blender, dalam perangkat lunak musik atau IDE untuk bahasa yang dikompilasi secara statis yang melakukan kompilasi langsung, dll. Alih-alih alat eksternal, bahasa dinamis lebih memilih alat yang menggunakan informasi yang sudah ada. Semua penelepon FOO yang dikenal? semua subkelas BAR? semua metode yang dikhususkan oleh kelas ZOT? ini adalah data yang diinternalisasi. Jenis hanyalah salah satu aspek dari ini.


(lihat juga: CFFI )

coredump
sumber
39

Jawaban singkat: tidak, karena kesetaraan Turing.

Jawaban panjang: Orang ini menjadi troll. Memang benar bahwa sistem tipe "membatasi Anda untuk sebuah himpunan bagian," hal-hal di luar bagian itu, menurut definisi, hal-hal yang tidak berfungsi.

Apa pun yang dapat Anda lakukan dalam bahasa pemrograman Turing lengkap (yang merupakan bahasa yang dirancang untuk pemrograman tujuan umum, ditambah banyak yang tidak; itu adalah bar yang sangat rendah untuk dihapus dan ada beberapa contoh sistem menjadi Turing- lengkap tanpa disengaja) Anda dapat melakukan dalam bahasa pemrograman Turing-lengkap lainnya. Ini disebut "Turing equivalence," dan itu hanya berarti persis apa yang dikatakannya. Yang penting, itu tidak berarti bahwa Anda dapat melakukan hal lain dengan mudah dalam bahasa lain - beberapa orang akan berpendapat bahwa itulah inti dari menciptakan bahasa pemrograman baru: untuk memberi Anda cara yang lebih baik untuk melakukan tertentu hal-hal yang menyedot bahasa yang ada.

Sistem tipe dinamis, misalnya, dapat ditiru di atas sistem tipe OO statis dengan hanya mendeklarasikan semua variabel, parameter, dan mengembalikan nilai sebagai Objecttipe dasar dan kemudian menggunakan refleksi untuk mengakses data tertentu di dalam, jadi ketika Anda menyadari ini Anda melihat bahwa secara harfiah tidak ada yang dapat Anda lakukan dalam bahasa dinamis yang tidak dapat Anda lakukan dalam bahasa statis. Tetapi melakukannya dengan cara seperti itu akan sangat berantakan, tentu saja.

Pria dari kutipan itu benar bahwa tipe statis membatasi apa yang dapat Anda lakukan, tapi itu fitur penting, bukan masalah. Garis-garis di jalan membatasi apa yang dapat Anda lakukan di mobil Anda, tetapi apakah Anda menganggapnya terbatas, atau membantu? (Saya tahu saya tidak akan mau mengendarai mobil di jalan yang sibuk dan kompleks di mana tidak ada yang memberi tahu mobil-mobil pergi ke arah yang berlawanan untuk tetap di sisi mereka dan tidak datang ke tempat saya mengemudi!) Dengan membuat aturan yang dengan jelas menggambarkan apa dianggap perilaku yang tidak valid dan memastikan bahwa itu tidak akan terjadi, Anda sangat mengurangi kemungkinan terjadinya kecelakaan parah.

Juga, dia salah mengidentifikasi sisi lain. Bukannya "semua program menarik yang ingin Anda tulis akan berfungsi sebagai tipe", melainkan "semua program menarik yang ingin Anda tulis akan membutuhkan tipe." Setelah Anda melampaui tingkat kerumitan tertentu, menjadi sangat sulit untuk mempertahankan basis kode tanpa sistem tipe untuk membuat Anda sejalan, karena dua alasan.

Pertama, karena kode tanpa anotasi jenis sulit dibaca. Pertimbangkan Python berikut:

def sendData(self, value):
   self.connection.send(serialize(value.someProperty))

Apa yang Anda harapkan dari data yang terlihat seperti yang diterima sistem di ujung koneksi yang lain? Dan jika itu menerima sesuatu yang tampak sangat salah, bagaimana Anda mengetahui apa yang terjadi?

Itu semua tergantung pada struktur value.someProperty. Tapi seperti apa bentuknya? Pertanyaan bagus! Memanggil apa sendData()? Apa yang lewat? Seperti apa variabel itu? Dari mana asalnya? Jika ini bukan lokal, Anda harus menelusuri seluruh sejarah valueuntuk melacak apa yang terjadi. Mungkin Anda melewati sesuatu yang juga memiliki somePropertyproperti, tetapi tidak melakukan apa yang Anda pikirkan?

Sekarang mari kita lihat dengan anotasi jenis, seperti yang mungkin Anda lihat dalam bahasa Boo, yang menggunakan sintaksis yang sangat mirip tetapi diketik secara statis:

def SendData(value as MyDataType):
   self.Connection.Send(Serialize(value.SomeProperty))

Jika ada yang tidak beres, tiba-tiba tugas debugging Anda menjadi lebih mudah: lihat definisi MyDataType! Plus, kemungkinan mendapatkan perilaku buruk karena Anda melewati beberapa jenis yang tidak kompatibel yang juga memiliki properti dengan nama yang sama tiba-tiba menjadi nol, karena sistem jenis tidak akan membiarkan Anda melakukan kesalahan itu.

Alasan kedua didasarkan pada yang pertama: dalam proyek besar dan kompleks, Anda kemungkinan besar mendapat banyak kontributor. (Dan jika tidak, Anda membangunnya sendiri untuk waktu yang lama, yang pada dasarnya adalah hal yang sama. Coba baca kode yang Anda tulis 3 tahun yang lalu jika Anda tidak percaya kepada saya!) Ini berarti Anda tidak tahu apa yang dulu melalui kepala orang yang menulis hampir semua bagian kode yang diberikan pada saat mereka menulisnya, karena Anda tidak ada di sana, atau tidak ingat apakah itu kode Anda sendiri dulu. Memiliki tipe deklarasi benar-benar membantu Anda memahami apa maksud kode itu!

Orang-orang seperti lelaki dalam kutipan sering salah mengartikan manfaat dari pengetikan statis sebagai tentang "membantu kompiler" atau "semua tentang efisiensi" di dunia di mana sumber daya perangkat keras yang hampir tak terbatas membuatnya semakin tidak relevan dengan setiap tahun yang lewat. Tetapi seperti yang telah saya tunjukkan, sementara manfaat-manfaat itu memang ada, manfaat utama ada pada faktor manusia, khususnya keterbacaan kode dan pemeliharaan. (Efisiensi tambahan tentu saja merupakan bonus yang bagus!)

Mason Wheeler
sumber
24
"Orang ini menjadi troll." - Saya tidak yakin serangan ad hominem akan membantu kasus Anda yang disajikan dengan baik. Dan sementara saya sadar bahwa argumen dari otoritas adalah kesalahan yang sama buruknya dengan ad hominem, saya masih ingin menunjukkan bahwa Gilad Bracha mungkin telah mendesain lebih banyak bahasa dan (paling relevan untuk diskusi ini) sistem tipe yang lebih statis daripada kebanyakan. Hanya kutipan kecil: ia adalah perancang tunggal Newspeak, co-desainer Dart, co-penulis Spesifikasi Bahasa Jawa dan Spesifikasi Mesin Virtual Java, bekerja pada desain Java dan JVM, dirancang…
Jörg W Mittag
10
Strongtalk (sistem tipe statis untuk Smalltalk), sistem tipe Dart, sistem tipe Newspeak, tesis PhD-nya tentang modularitas adalah dasar dari hampir semua sistem modul modern (misalnya Java 9, ECMAScript 2015, Scala, Dart, Newspeak, Ioke , Seph), makalahnya tentang mixin merevolusi cara kita berpikir tentang mereka. Sekarang, itu tidak berarti bahwa dia benar, tapi saya tidak berpikir bahwa setelah dirancang beberapa sistem jenis statis membuatnya sedikit lebih dari "troll".
Jörg W Mittag
17
"Meskipun benar bahwa sistem tipe" membatasi Anda untuk sebuah subset, "hal-hal di luar subset itu, menurut definisi, hal-hal yang tidak berfungsi." - Ini salah. Kita tahu dari Undecidability of the Halting Problem, Rice's Theorem, dan segudang hasil Undecidability dan Uncomputability lainnya bahwa pemeriksa tipe statis tidak dapat memutuskan untuk semua program apakah mereka aman atau tidak aman. Itu tidak dapat menerima program-program tersebut (beberapa di antaranya adalah tipe-tidak aman), jadi satu-satunya pilihan yang waras adalah menolaknya (namun, beberapa di antaranya adalah tipe-aman). Atau, bahasanya harus dirancang dalam…
Jörg W Mittag
9
... sedemikian rupa untuk membuat tidak mungkin bagi programmer untuk menulis program yang tidak dapat ditentukan, tetapi sekali lagi, beberapa dari mereka sebenarnya tipe-safe. Jadi, tidak masalah bagaimana Anda mengirisnya: programmer dicegah menulis program yang aman. Dan karena sebenarnya ada jauh lebih banyak dari mereka (biasanya), kita dapat hampir yakin bahwa setidaknya beberapa dari mereka tidak hanya hal-hal yang tidak bekerja, tetapi juga berguna.
Jörg W Mittag
8
@MasonWheeler: Masalah Pemutusan muncul sepanjang waktu, tepatnya dalam konteks pemeriksaan tipe statis. Kecuali bahasa dirancang dengan hati-hati untuk mencegah programmer menulis jenis program tertentu, pengecekan tipe statis dengan cepat menjadi setara dengan memecahkan Masalah Berhenti. Entah Anda berakhir dengan program yang Anda tidak diizinkan untuk menulis karena mereka mungkin membingungkan jenis checker, atau Anda berakhir dengan program yang sedang diijinkan untuk menulis tetapi mereka mengambil jumlah tak terbatas waktu untuk jenis pemeriksaan.
Jörg W Mittag
27

Saya akan melangkah ke samping bagian 'pola' karena saya pikir itu beralih ke definisi apa yang merupakan pola dan tidak dan saya sudah lama kehilangan minat dalam perdebatan itu. Apa yang akan saya katakan adalah bahwa ada hal - hal yang dapat Anda lakukan dalam beberapa bahasa yang tidak dapat Anda lakukan dalam bahasa lain. Biar saya perjelas, saya tidak mengatakan ada masalah yang bisa Anda selesaikan dalam satu bahasa yang tidak bisa Anda selesaikan dalam bahasa lain. Mason sudah menunjukkan kelengkapan Turing.

Sebagai contoh, saya telah menulis sebuah kelas dengan python yang mengambil membungkus elemen DOM XML dan membuatnya menjadi objek kelas satu. Artinya, Anda dapat menulis kode:

doc.header.status.text()

dan Anda memiliki konten jalur itu dari objek XML yang diuraikan. agak rapi dan rapi, IMO. Dan jika tidak ada simpul kepala, itu hanya mengembalikan objek dummy yang tidak mengandung apa-apa kecuali objek dummy (kura-kura sepanjang jalan.) Tidak ada cara nyata untuk melakukan itu di, katakanlah, Jawa. Anda harus mengkompilasi kelas sebelumnya yang didasarkan pada beberapa pengetahuan tentang struktur XML. Mengesampingkan apakah ini ide yang baik, hal semacam ini benar-benar mengubah cara Anda memecahkan masalah dalam bahasa yang dinamis. Saya tidak mengatakan itu berubah dengan cara yang selalu selalu lebih baik. Ada beberapa biaya pasti untuk pendekatan dinamis dan jawaban Mason memberikan tinjauan yang layak. Apakah mereka pilihan yang baik tergantung pada banyak faktor.

Di samping catatan, Anda dapat melakukan ini di Jawa karena Anda dapat membangun juru bahasa python di Jawa . Fakta bahwa menyelesaikan masalah khusus dalam bahasa tertentu dapat berarti membangun juru bahasa atau sesuatu yang mirip dengan itu sering diabaikan ketika orang berbicara tentang kelengkapan Turing.

JimmyJames
sumber
4
Anda tidak dapat melakukan ini di Jawa karena Java dirancang dengan buruk. Tidak akan sesulit itu dalam menggunakan C # IDynamicMetaObjectProvider, dan itu sangat sederhana di Boo. ( Berikut ini implementasi dalam kurang dari 100 baris, termasuk sebagai bagian dari pohon sumber standar di GitHub, karena semudah itu!)
Mason Wheeler
6
@MasonWheeler "IDynamicMetaObjectProvider"? Apakah itu terkait dengan dynamickata kunci C # ? ... yang secara efektif hanya mengetuk pengetikan dinamis ke C #? Tidak yakin argumen Anda valid jika saya benar.
jpmc26
9
@MasonWheeler Anda masuk ke semantik. Tanpa masuk ke dalam perdebatan tentang hal-hal kecil (Kami tidak mengembangkan formalisme matematis pada SE di sini.), Pengetikan dinamis adalah praktik dari sebelumnya mengompilasi keputusan waktu seputar jenis, terutama verifikasi bahwa setiap jenis memiliki anggota tertentu yang diakses oleh program. Itulah tujuan yang dynamicdicapai dalam C #. "Refleksi dan pencarian kamus" terjadi saat runtime, bukan waktu kompilasi. Saya benar-benar tidak yakin bagaimana Anda dapat membuat case yang tidak menambahkan pengetikan dinamis ke bahasa. Maksud saya adalah paragraf terakhir Jimmy mencakup hal itu.
jpmc26
44
Meskipun bukan penggemar berat Java, saya juga berani mengatakan bahwa memanggil Java "dirancang dengan buruk" secara khusus karena tidak menambahkan pengetikan dinamis adalah ... terlalu bersemangat.
jpmc26
5
Terlepas dari sintaks yang sedikit lebih nyaman, apa bedanya dengan kamus?
Theodoros Chatzigiannakis
10

Kutipan itu benar, tetapi juga sangat tidak jujur. Mari kita uraikan alasannya:

Hal yang menakjubkan tentang pengetikan dinamis adalah Anda dapat mengekspresikan apa pun yang dapat dikomputasi.

Ya tidak cukup. Sebuah bahasa dengan mengetik dinamis memungkinkan Anda untuk mengekspresikan apapun selama itu Turing lengkap , yang sebagian besar adalah. Sistem tipe itu sendiri tidak memungkinkan Anda mengekspresikan segalanya. Mari kita beri dia manfaat dari keraguan di sini.

Dan sistem tipe jangan - sistem tipe biasanya decidable, dan mereka membatasi Anda untuk subset.

Ini benar, tetapi perhatikan bahwa kita sekarang dengan tegas berbicara tentang apa yang diperbolehkan oleh sistem tipe , bukan apa bahasa yang menggunakan sistem tipe memungkinkan. Meskipun dimungkinkan untuk menggunakan sistem tipe untuk menghitung hal-hal pada waktu kompilasi, ini pada umumnya tidak menyelesaikan Turing (karena sistem tipe umumnya dapat dipilih), tetapi hampir semua bahasa yang diketik secara statis juga Turing lengkap dalam runtime-nya (bahasa yang diketik secara dependen adalah tidak, tapi saya tidak percaya kita membicarakannya di sini).

Orang-orang yang menyukai sistem tipe statis berkata “tidak apa-apa, itu cukup baik; semua program menarik yang ingin Anda tulis akan berfungsi sebagai tipe ”. Tapi itu konyol - sekali Anda memiliki sistem tipe, Anda bahkan tidak tahu program apa yang menarik.

Masalahnya adalah bahwa bahasa jenis dinamis memang memiliki tipe statis. Kadang-kadang semuanya adalah string, dan lebih umum ada beberapa tagged union di mana setiap hal adalah sekumpulan properti atau nilai seperti int atau double. Masalahnya adalah bahwa bahasa statis dapat melakukan ini juga, secara historis itu agak clunkier untuk melakukan ini, tetapi bahasa yang diketik secara statis modern membuat ini cukup mudah dilakukan seperti menggunakan bahasa jenis dinamis, jadi bagaimana mungkin ada perbedaan dalam apa yang bisa dilihat oleh programmer sebagai program yang menarik? Bahasa statis memiliki serikat tag yang persis sama dengan jenis lainnya.

Untuk menjawab pertanyaan dalam judul: Tidak, tidak ada pola desain yang tidak dapat diimplementasikan dalam bahasa yang diketik secara statis, karena Anda selalu dapat menerapkan cukup dari sistem dinamis untuk mendapatkannya. Mungkin ada pola yang Anda dapatkan secara 'gratis' dalam bahasa yang dinamis; ini mungkin layak atau tidak layak untuk digunakan dengan kelemahan bahasa-bahasa tersebut untuk YMMV .

jk.
sumber
2
Saya tidak sepenuhnya yakin apakah Anda baru saja menjawab ya atau tidak. Kedengarannya lebih seperti tidak bagi saya.
user7610
1
@TheodorosChatzigiannakis Ya, bagaimana lagi bahasa dinamis akan diterapkan? Pertama, Anda akan dianggap sebagai arsitek astronot jika Anda ingin menerapkan sistem kelas dinamis atau hal lain yang sedikit terlibat. Kedua, Anda mungkin tidak memiliki sumber daya untuk menjadikannya debuggable, introspectable, performant ("hanya menggunakan kamus" adalah bagaimana bahasa lambat diimplementasikan). Ketiga, beberapa fitur dinamis yang terbaik digunakan bila yang terintegrasi di seluruh bahasa, bukan hanya sebagai perpustakaan: berpikir pengumpulan sampah misalnya (ada yang GCS sebagai perpustakaan, tetapi mereka tidak umum digunakan).
coredump
1
@Theodoros Menurut makalah yang telah saya tautkan di sini sekali, semua kecuali 2,5% dari struktur (dalam modul Python yang diteliti oleh para peneliti) dapat dengan mudah diekspresikan dalam bahasa yang diketik. Mungkin 2,5% membuat membayar biaya pengetikan dinamis sepadan. Pada dasarnya itulah pertanyaan saya. neverworkintheory.org/2016/06/13/polymorphism-in-python.html
user7610
3
@ JiriDanek Sejauh yang saya tahu, tidak ada yang mencegah bahasa yang diketik secara statis dari memiliki tempat panggilan polimorfik dan mempertahankan pengetikan statis dalam proses. Lihat Memeriksa Tipe Statis dari Multi-Metode . Mungkin saya salah memahami tautan Anda.
Theodoros Chatzigiannakis
1
"Bahasa dengan pengetikan dinamis memungkinkan Anda mengekspresikan apa pun asalkan Turing lengkap, yang kebanyakan adalah." Walaupun ini tentu saja pernyataan yang benar, itu tidak benar - benar berlaku di "dunia nyata" karena jumlah teks yang dimiliki seseorang menulis bisa sangat besar.
Daniel Jour
4

Pasti ada hal-hal yang hanya dapat Anda lakukan dalam bahasa yang diketik secara dinamis. Tapi mereka tidak harus desain yang bagus .

Anda dapat menetapkan bilangan bulat 5 lalu string 'five', atau Catobjek, ke variabel yang sama. Tetapi Anda hanya mempersulit pembaca kode Anda untuk mencari tahu apa yang terjadi, apa tujuan dari setiap variabel.

Anda dapat menambahkan metode baru ke kelas Ruby library dan mengakses bidang privasinya. Mungkin ada kasus-kasus di mana peretasan seperti itu bisa berguna tetapi ini akan menjadi pelanggaran enkapsulasi. (Saya tidak keberatan menambahkan metode yang hanya mengandalkan antarmuka publik, tetapi tidak ada yang tidak dapat dilakukan metode ekstensi C # yang diketik secara statis.)

Anda bisa menambahkan bidang baru ke objek kelas orang lain untuk memberikan beberapa data tambahan dengannya. Tapi itu desain yang lebih baik untuk hanya membuat struktur baru, atau memperluas tipe aslinya.

Secara umum, semakin terorganisir Anda ingin kode Anda tetap, semakin sedikit keuntungan yang harus Anda ambil dari dapat secara dinamis mengubah definisi jenis atau menetapkan nilai dari berbagai jenis ke variabel yang sama. Tetapi kemudian kode Anda tidak berbeda dari apa yang dapat Anda capai dalam bahasa yang diketik secara statis.

Apa bahasa dinamis yang baik adalah gula sintaksis. Sebagai contoh, ketika membaca objek JSON deserialized Anda dapat merujuk ke nilai bersarang hanya obj.data.article[0].content- lebih rapi daripada mengatakan obj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content").

Pengembang Ruby terutama dapat berbicara panjang lebar tentang sihir yang dapat dicapai dengan mengimplementasikan method_missing, yang merupakan metode yang memungkinkan Anda untuk menangani panggilan percobaan ke metode yang tidak dideklarasikan. Misalnya, ActiveRecord ORM menggunakannya sehingga Anda dapat melakukan panggilan User.find_by_email('[email protected]')tanpa harus mendeklarasikan find_by_emailmetode. Tentu saja itu bukan apa-apa yang tidak dapat dicapai seperti UserRepository.FindBy("email", "[email protected]")dalam bahasa yang diketik secara statis, tetapi Anda tidak dapat menyangkal kerapiannya.

kamilk
sumber
4
Pasti ada hal-hal yang hanya dapat Anda lakukan dalam bahasa yang diketik secara statis. Tapi mereka tidak harus desain yang bagus.
coredump
2
Poin tentang gula sintaksis sangat sedikit hubungannya dengan pengetikan dinamis dan semuanya dengan sintaksis.
leftaroundabout
@leftaroundabout Patterns memiliki semua yang harus dilakukan dengan sintaks. Jenis sistem juga ada banyak hubungannya dengan itu.
user253751
4

Pola Proksi Dinamis adalah cara pintas untuk mengimplementasikan objek proxy tanpa perlu satu kelas per jenis yang Anda perlu proxy.

class Proxy(object):
    def __init__(self, obj):
        self.__target = obj

    def __getattr__(self, attr):
        return getattr(self.__target, attr)

Menggunakan ini, Proxy(someObject)membuat objek baru yang berperilaku sama dengan someObject. Tentunya Anda juga ingin menambahkan fungsionalitas tambahan, tetapi ini adalah dasar yang berguna untuk memulai. Dalam bahasa statis lengkap, Anda harus menulis satu kelas Proxy per jenis yang ingin Anda proksi atau menggunakan pembuatan kode dinamis (yang, diakui, termasuk dalam pustaka standar banyak bahasa statis, terutama karena perancang mereka sadar akan masalah yang tidak dapat dilakukan karena hal ini).

Kasus penggunaan lain dari bahasa dinamis adalah apa yang disebut "tambalan monyet". Dalam banyak hal, ini adalah anti-pola daripada pola, tetapi dapat digunakan dengan cara yang bermanfaat jika dilakukan dengan hati-hati. Dan sementara tidak ada alasan teoritis patch monyet tidak dapat diimplementasikan dalam bahasa statis, saya belum pernah melihat yang benar-benar memilikinya.

Jules
sumber
Saya pikir saya mungkin bisa meniru ini di Go. Ada serangkaian metode yang harus dimiliki oleh semua objek yang diproksi (jika tidak, bebek mungkin tidak akan berdetak dan semua berantakan). Saya dapat membuat antarmuka Go dengan metode ini. Saya harus lebih memikirkannya, tetapi saya pikir apa yang ada dalam pikiran saya akan berhasil.
user7610
Anda dapat sesuatu yang serupa dalam bahasa .NET apa pun dengan RealProxy dan generik.
LittleEwok
@LittleEwok - RealProxy menggunakan pembuatan kode runtime - seperti yang saya katakan, banyak bahasa statis modern memiliki solusi seperti ini, tetapi masih lebih mudah dalam bahasa yang dinamis.
Jules
Metode ekstensi C # agak seperti menambal monyet dibuat aman. Anda tidak dapat mengubah metode yang ada, tetapi Anda bisa menambahkan yang baru.
Andrew mengatakan Reinstate Monica
3

iya nih , ada banyak pola dan teknik yang hanya mungkin dilakukan dalam bahasa yang diketik secara dinamis.

Monkey patching adalah teknik di mana properti atau metode ditambahkan ke objek atau kelas saat runtime. Teknik ini tidak mungkin dalam bahasa yang diketik secara statis karena ini berarti jenis dan operasi tidak dapat diverifikasi pada waktu kompilasi. Atau dengan kata lain, jika suatu bahasa mendukung tambalan-monyet, itu adalah definisi bahasa yang dinamis.

Dapat dibuktikan bahwa jika suatu bahasa mendukung tambalan monyet (atau teknik serupa untuk memodifikasi tipe saat runtime), itu tidak dapat secara statis diketik. Jadi itu bukan hanya batasan dalam bahasa yang ada saat ini, itu adalah batasan mendasar dari pengetikan statis.

Jadi kutipannya benar - lebih banyak hal yang mungkin dalam bahasa yang dinamis daripada dalam bahasa yang diketik secara statis. Di sisi lain, beberapa jenis analisis hanya mungkin dilakukan dalam bahasa yang diketik secara statis. Misalnya Anda selalu tahu operasi mana yang diizinkan pada tipe tertentu, yang memungkinkan Anda mendeteksi operasi ilegal pada tipe kompilasi. Tidak ada verifikasi seperti itu dalam bahasa yang dinamis ketika operasi dapat ditambahkan atau dihapus pada saat runtime.

Inilah sebabnya mengapa tidak ada yang "terbaik" dalam konflik antara bahasa statis dan dinamis. Bahasa statis melepaskan kekuatan tertentu pada saat runtime dengan imbalan jenis daya yang berbeda pada waktu kompilasi, yang mereka yakini mengurangi jumlah bug dan membuat pengembangan lebih mudah. Beberapa percaya bahwa pertukaran itu sepadan, yang lain tidak.

Jawaban lain berpendapat bahwa Turing-equivalence berarti segala sesuatu yang mungkin dalam satu bahasa adalah mungkin di semua bahasa. Tetapi ini tidak mengikuti. Untuk mendukung sesuatu seperti penambalan monyet dalam bahasa statis, Anda pada dasarnya harus mengimplementasikan sub-bahasa dinamis di dalam bahasa statis. Ini tentu saja mungkin, tetapi saya berpendapat Anda kemudian memprogram dalam bahasa dinamis yang tertanam, karena Anda juga kehilangan pemeriksaan tipe statis yang ada dalam bahasa host.

C # sejak versi 4 telah mendukung objek yang diketik secara dinamis. Jelas bahwa perancang bahasa melihat manfaat dari memiliki kedua jenis pengetikan yang tersedia. Tapi itu juga menunjukkan Anda tidak dapat memiliki kue dan makan saya juga: Ketika Anda menggunakan objek dinamis di C # Anda mendapatkan kemampuan melakukan sesuatu seperti patch monyet, tetapi Anda juga kehilangan verifikasi tipe statis untuk interaksi dengan objek-objek ini.

JacquesB
sumber
+1 paragraf kedua hingga terakhir Anda pikir saya adalah argumen penting. Saya masih berpendapat bahwa ada perbedaan meskipun seperti dengan jenis statis Anda memiliki kontrol penuh di mana dan apa yang Anda dapat monyet tambal
jk.
2

Saya bertanya-tanya, adakah pola atau strategi desain yang berguna yang, dengan menggunakan rumusan kutipan, "tidak berfungsi sebagai tipe"?

Iya dan tidak.

Ada situasi di mana programmer mengetahui tipe variabel dengan lebih presisi daripada kompiler. Compiler mungkin tahu bahwa sesuatu adalah Object, tetapi programmer akan tahu (karena invarian dari program) bahwa itu sebenarnya sebuah String.

Izinkan saya menunjukkan beberapa contoh ini:

Map<Class<?>, Function<?, String>> someMap;
someMap.get(object.getClass()).apply(object);

Saya tahu itu someMap.get(T.class)akan mengembalikan a Function<T, String>, karena bagaimana saya membangun someMap. Tetapi Java hanya yakin bahwa saya memiliki sebuah Function.

Contoh lain:

data = parseJSON(someJson)
validate(data, someJsonSchema);
print(data.properties.rowCount);

Saya tahu bahwa data.properties.rowCount akan menjadi referensi dan bilangan bulat yang valid, karena saya telah memvalidasi data terhadap suatu skema. Jika bidang itu hilang, pengecualian akan dilemparkan. Tetapi seorang kompiler hanya akan tahu bahwa apakah melempar pengecualian atau mengembalikan semacam JSONValue generik.

Contoh lain:

x, y, z = struct.unpack("II6s", data)

"II6s" mendefinisikan cara data menyandikan tiga variabel. Karena saya sudah menentukan formatnya, saya tahu tipe mana yang akan dikembalikan. Kompiler hanya akan tahu bahwa ia mengembalikan tuple.

Tema pemersatu dari semua contoh ini adalah bahwa pemrogram mengetahui tipe tersebut, tetapi sistem tipe level Java tidak akan dapat mencerminkan hal itu. Kompiler tidak akan tahu jenisnya, dan dengan demikian bahasa yang diketik secara statis tidak akan membiarkan saya memanggilnya, sedangkan bahasa yang diketik secara dinamis akan.

Itulah kutipan asli:

Hal yang menakjubkan tentang pengetikan dinamis adalah Anda dapat mengekspresikan apa pun yang dapat dikomputasi. Dan sistem tipe jangan - sistem tipe biasanya decidable, dan mereka membatasi Anda untuk subset.

Saat menggunakan pengetikan dinamis, saya bisa menggunakan tipe turunan paling banyak yang saya tahu, bukan hanya tipe turunan yang paling diketahui sistem tipe bahasa saya. Dalam semua kasus di atas, saya memiliki kode yang secara semantik benar, tetapi akan ditolak oleh sistem pengetikan statis.

Namun, untuk kembali ke pertanyaan Anda:

Saya bertanya-tanya, adakah pola atau strategi desain yang berguna yang, dengan menggunakan rumusan kutipan, "tidak berfungsi sebagai tipe"?

Salah satu contoh di atas, dan memang contoh apa pun dari pengetikan dinamis dapat dibuat valid dalam pengetikan statis dengan menambahkan gips yang sesuai. Jika Anda tahu tipe kompiler Anda tidak, cukup beri tahu kompiler dengan memberikan nilai. Jadi, pada tingkat tertentu, Anda tidak akan mendapatkan pola tambahan dengan menggunakan pengetikan dinamis. Anda mungkin perlu melakukan lebih banyak untuk menjalankan kode yang diketik secara statis.

Keuntungan dari pengetikan dinamis adalah Anda cukup menggunakan pola-pola ini tanpa mengkhawatirkan fakta bahwa sulit untuk meyakinkan sistem tipe Anda tentang validitasnya. Itu tidak mengubah pola yang tersedia, itu hanya mungkin membuatnya lebih mudah untuk diterapkan karena Anda tidak perlu mencari cara untuk membuat sistem tipe Anda mengenali pola atau menambahkan gips untuk menumbangkan sistem tipe.

Winston Ewert
sumber
1
mengapa java titik potong di mana Anda tidak harus pergi ke 'sistem tipe yang lebih maju / rumit'?
jk.
2
Oh, apa yang membuatmu berpikir itu yang aku katakan? Saya secara eksplisit menghindari berpihak pada apakah sistem tipe yang lebih maju / rumit bermanfaat.
Winston Ewert
2
Beberapa di antaranya adalah contoh yang mengerikan, dan yang lain tampaknya lebih banyak keputusan bahasa daripada yang diketik vs yang tidak diketik. Saya khususnya bingung mengapa orang berpikir deserialisasi begitu rumit dalam bahasa yang diketik. Hasil yang diketik akan menjadi data = parseJSON<SomeSchema>(someJson); print(data.properties.rowCount); dan jika seseorang tidak memiliki kelas untuk deserialize ke kita dapat kembali ke data = parseJSON(someJson); print(data["properties.rowCount"]);- yang masih diketik dan mengekspresikan maksud yang sama.
NPSF3000
2
@ NPSF3000, bagaimana cara kerja fungsi parseJSON? Tampaknya akan menggunakan refleksi atau makro. Bagaimana data ["properties.rowCount"] dapat diketik dalam bahasa statis? Bagaimana ia bisa tahu bahwa nilai yang dihasilkan adalah bilangan bulat?
Winston Ewert
2
@ NPSF3000, bagaimana Anda berencana menggunakannya jika Anda tidak tahu itu bilangan bulat? Bagaimana Anda berencana untuk mengulang elemen di daftar di JSON tanpa mengetahui bahwa itu adalah array? Inti dari contoh saya adalah bahwa saya tahu itu data.propertiesadalah objek dan saya tahu itu data.properties.rowCountadalah bilangan bulat dan saya hanya bisa menulis kode yang menggunakannya. Usulan data["properties.rowCount"]Anda tidak memberikan hal yang sama.
Winston Ewert
1

Berikut adalah beberapa contoh dari Objective-C (diketik secara dinamis) yang tidak mungkin di C ++ (diketik secara statis):

  • Menempatkan objek dari beberapa kelas yang berbeda ke dalam wadah yang sama.
    Tentu saja, ini membutuhkan inspeksi tipe runtime untuk kemudian menginterpretasikan isi dari wadah, dan sebagian besar teman dari pengetikan statis akan keberatan bahwa Anda seharusnya tidak melakukan ini sejak awal. Tetapi saya telah menemukan bahwa, di luar perdebatan agama, ini bisa berguna.

  • Memperluas kelas tanpa subclassing.
    Di Objective-C, Anda bisa mendefinisikan fungsi anggota baru untuk kelas yang ada, termasuk yang seperti bahasa NSString. Misalnya, Anda dapat menambahkan metode stripPrefixIfPresent:, sehingga Anda dapat mengatakan [@"foo/bar/baz" stripPrefixIfPresent:@"foo/"](perhatikan penggunaan NSSringliteral @"").

  • Penggunaan panggilan balik berorientasi objek.
    Dalam bahasa yang diketik secara statis seperti Java dan C ++, Anda harus berusaha cukup lama untuk memungkinkan pustaka memanggil anggota sewenang-wenang dari objek yang disediakan pengguna. Di Jawa, solusinya adalah pasangan antarmuka / adaptor plus kelas anonim, dalam C ++ solusinya biasanya berbasis template, yang menyiratkan bahwa kode perpustakaan harus diekspos ke kode pengguna. Di Objective-C, Anda hanya meneruskan referensi objek plus pemilih untuk metode ke pustaka, dan pustaka dapat dengan mudah dan langsung memanggil kembali.

cmaster
sumber
Saya bisa melakukan yang pertama di C ++ dengan casting untuk membatalkan *, tapi itu menghindari sistem tipe, jadi tidak masuk hitungan. Saya dapat melakukan yang kedua dalam C # dengan metode ekstensi, sempurna dalam sistem tipe. Untuk yang ketiga, saya pikir "pemilih untuk metode" bisa menjadi lambda, jadi bahasa yang diketik secara statis dengan lambdas dapat melakukan hal yang sama, jika saya mengerti dengan benar. Saya tidak terbiasa dengan ObjC.
user7610
1
@JiriDanek "Saya bisa melakukan yang pertama di C ++ dengan casting ke void *", tidak persis, kode yang membaca elemen tidak memiliki cara untuk mengambil tipe aktual sendiri. Anda perlu mengetik tag. Selain itu, saya tidak berpikir mengatakan "Saya bisa melakukan ini dalam <bahasa>" adalah cara yang tepat / produktif untuk melihat ini, karena Anda selalu dapat meniru mereka. Yang penting adalah keuntungan dalam ekspresivitas vs kompleksitas implementasi. Juga, Anda tampaknya berpikir bahwa jika suatu bahasa memiliki kemampuan statis dan dinamis (Java, C #), bahasa tersebut dimiliki secara eksklusif oleh keluarga bahasa "statis".
coredump
1
@JiriDanek void*sendiri bukan pengetikan dinamis, ini kurang pengetikan. Tapi ya, dynamic_cast, tabel virtual dll. Membuat C ++ tidak diketik secara statis. Apakah itu buruk?
coredump
1
Ini menunjukkan bahwa memiliki opsi untuk merongrong sistem tipe saat dibutuhkan berguna. Memiliki pintu darurat ketika Anda membutuhkannya. Atau seseorang menganggapnya berguna. Kalau tidak, mereka tidak akan memasukkannya ke dalam bahasa.
user7610
2
@JiriDanek Saya pikir, Anda cukup berhasil dengan komentar terakhir Anda. Pintu darurat itu bisa sangat berguna jika digunakan dengan hati-hati. Namun demikian, dengan kekuatan besar datang tanggung jawab besar, dan banyak adalah orang-orang yang menyalahgunakannya ... Dengan demikian, rasanya jauh lebih baik menggunakan pointer ke kelas dasar generik bahwa semua kelas lain berasal dari definisi (seperti halnya baik di Objective-C dan Java), dan bergantung pada RTTI untuk membedakan kasus, daripada untuk melemparkan void*ke beberapa jenis objek tertentu. Yang pertama menghasilkan kesalahan runtime jika Anda mengacaukan, hasil kemudian dalam perilaku yang tidak terdefinisi.
cmaster