Чи існують шаблони дизайну, які можливі лише на динамічно набраних мовах, як-от Python?


30

Я прочитав відповідне запитання. Чи існують непотрібні шаблони дизайну в таких динамічних мовах, як Python? і згадав цю цитату на Wikiquote.org

Дивовижна річ щодо динамічного набору тексту - це дозволяє висловити все, що можна обчислити. А системи типів не типу типів, як правило, можна вирішити, і вони обмежують вас підмножиною. Люди, які віддають перевагу системам статичного типу, кажуть: «це добре, це досить добре; всі цікаві програми, які ви хочете написати, працюватимуть як типи ». Але це смішно - раз у вас є система типу, ви навіть не знаєте, які цікаві програми є.

--- Програмне забезпечення радіоепізоду 140: типи новомовних та підключених з Gilad Bracha

Цікаво, чи існують корисні схеми дизайну чи стратегії, які, використовуючи формулювання цитати, «не працюють як типи»?


3
Я виявив, що подвійне відправлення та шаблон відвідувачів дуже важко здійснити на мовах статичного типу, але легко виконати на динамічних мовах. Дивіться цю відповідь (і питання), наприклад: programmers.stackexchange.com/a/288153/122079
user3002473

7
Звичайно. Наприклад, будь-яка модель, яка передбачає створення нових класів під час виконання. (це теж можливо в Java, але не в C ++; є розсувна шкала динамізму).
користувач253751

1
Це дуже залежатиме від того, наскільки складною є система вашого типу :-) Функціональні мови зазвичай в цьому непогані.
Бергі

1
Здається, всі говорять про типові системи типу Java та C # замість Haskell чи OCaml. Мова з потужною системою типу може бути такою ж стислою, як і динамічна мова, але зберігати безпеку типу.
Ендрю каже, що знову повернеться до Моніки

@immibis Це неправильно. Системи статичного типу можуть абсолютно створювати нові, "динамічні" класи під час виконання. Див. Розділ 33 Практичні основи для мов програмування.
садок

Відповіді:


4

Першокласні типи

Динамічне введення означає, що у вас є першокласні типи: ви можете перевіряти, створювати та зберігати типи під час виконання, включаючи власні типи мови. Це також означає, що значення вводяться, а не змінні .

Статично типізована мова може створювати код, який також покладається на динамічні типи, як диспетчер методів, класи типів тощо, але таким чином, як правило, невидимий для виконання. У кращому випадку вони дають вам певний спосіб здійснити самоаналіз. Крім того, ви можете імітувати типи як значення, але тоді у вас є спеціальна система динамічного типу.

Однак системи динамічного типу рідко мають лише першокласні типи. Ви можете мати першокласні символи, першокласні пакунки, першокласні .... все. Це суперечить суворому розділенню між мовою компілятора та мовою виконання на стаціонарних мовах. Те, що може робити компілятор або інтерпретатор, виконує також.

Тепер давайте погодимось, що висновок типу - це добре, і що мені подобається перевірити свій код перед його запуском. Однак мені також подобається можливість створювати та компілювати код під час виконання. І я люблю попередньо обчислювати речі і під час компіляції. У динамічно набраній мові це робиться з тією ж мовою. В OCaml у вас є система типу модуль / функтор, яка відрізняється від системи основного типу, яка відрізняється від мови препроцесора. У C ++ у вас є мова шаблонів, яка не має нічого спільного з основною мовою, яка, як правило, не знає типів під час виконання. І це добре в тій мові, оскільки вони не хочуть надавати більше.

Зрештою, це не змінює насправді, яке саме програмне забезпечення ви можете розробити, але виразність змінює те, як ви їх розробляєте, і важко чи ні.

Візерунки

Шаблони, що покладаються на динамічні типи, - це шаблони, що включають динамічне середовище: відкриті класи, диспетчеризація, бази даних в пам'яті об'єктів, серіалізація тощо. Прості речі, такі як загальні контейнери, працюють тому, що вектор не забуває під час виконання про тип об'єктів, які він містить (немає необхідності в параметричних типах).

Я спробував представити багато способів оцінювання коду в Common Lisp, а також приклади можливих статичних аналізів (це SBCL). Приклад пісочниці компілює крихітний підмножина коду Lisp, отриманого з окремого файлу. Для того, щоб бути досить безпечним, я змінюю читану, дозволяю лише підмножину стандартних символів і обертаю речі тимчасовим часом.

;;
;; 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}>)

Нічого, що вище, неможливо зробити з іншими мовами. Підхід плагіну в Blender, в музичному програмному забезпеченні або IDE для статично складених мов, які роблять під час рекомпіляції та ін. Замість зовнішніх інструментів динамічні мови надають перевагу інструментам, які використовують інформацію, яка вже є. Всім відомим абонентам FOO? всі підкласи BAR? всі методи, спеціалізовані за класом ZOT? це інтерналізовані дані. Види - це ще один аспект цього.


(див. також: CFFI )


39

Коротка відповідь: ні, тому що еквівалентність Тьюрінга.

Довга відповідь: Цей хлопець є троллем. Хоча це правда, що системи типів "обмежують вас підмножиною", речі за межами цього підмножини, за визначенням, це речі, які не працюють.

Все, що ви можете зробити на будь-якій мові програмування, повністю завершеній Тьюрінгом (це мова, призначена для програмування загального призначення, а також багато, чого немає; це досить низька смуга для очищення, і є кілька прикладів того, що система стає Тьюрінґ- Ви можете це зробити ненавмисно) будь-якою іншою мовою програмування Тьюрінга. Це називається "еквівалентність Тюрінга", і це означає лише те, що він говорить. Що важливо, це не означає, що ви можете зробити іншу справу так само легко на іншій мові - дехто може стверджувати, що в цьому і полягає вся суть створення нової мови програмування: щоб вам було краще робити певні дії речі, які всмоктують існуючі мови.

Наприклад, система динамічного типу може бути емульована поверх статичної системи типу OO, просто оголосивши всі змінні, параметри та значення повернення як базовий Objectтип, а потім використовувати відображення для доступу до певних даних усередині, тож коли ви усвідомлюєте це ви бачите, що буквально нічого не можна зробити в динамічній мові, чого неможливо зробити статичною мовою. Але зробити це таким чином було б величезним безладом, звичайно.

Хлопець із цитати правильний, що статичні типи обмежують те, що можна зробити, але це важлива особливість, а не проблема. Лінії на дорозі обмежують те, що ви можете зробити у своєму автомобілі, але чи вважаєте ви їх обмежуючими чи корисними? (Я знаю, що я не хотів би їхати по зайнятій, складній дорозі, де нічого не сказати автомобілям, що рухаються у зворотному напрямку, щоб не триматися на своїй стороні, а не переходити туди, де я їду!) Встановлюючи правила, які чітко окреслюють те, що вважаючи недійсною поведінку і гарантуючи, що цього не відбудеться, ви значно знижуєте шанси на випадкові аварії.

Крім того, він неправильно характеризує іншу сторону. Справа не в тому, що "всі цікаві програми, які ви хочете написати, працюватимуть як типи", а, скоріше, "всі цікаві програми, які ви хочете написати, потребують типів". Як тільки ви виходите за певний рівень складності, дуже важко підтримувати базу коду без типової системи, щоб тримати вас в порядку з двох причин.

По-перше, тому що код без анотацій типу важко читати. Розглянемо наступний Python:

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

Як ви очікуєте, що дані виглядатимуть так, що система отримує на іншому кінці з'єднання? І якщо він отримує щось, що виглядає абсолютно не так, як ви зрозумієте, що відбувається?

Все залежить від структури value.someProperty. Але як це виглядає? Гарне питання! Що дзвонить sendData()? Що це проходить? Як виглядає ця змінна? Звідки воно взялося? Якщо це не місцево, вам доведеться простежити всю історію, valueщоб відстежити, що відбувається. Можливо, ви передаєте щось інше, що також має somePropertyвластивість, але це не робить те, що ви думаєте, що робить?

Тепер давайте розглянемо це з анотаціями типів, як ви могли бачити в мові Boo, яка використовує дуже схожий синтаксис, але статично набрана:

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

Якщо щось відбувається не так, раптом ваша робота з налагодження просто набрала на порядок простіше: шукайте визначення MyDataType! Крім того, шанс отримати погану поведінку, оскільки ви передали якийсь несумісний тип, який також має властивість з тим самим іменем, раптом переходить до нуля, оскільки система типу не дозволить вам помилитися.

Друга причина ґрунтується на першій: у великому та складному проекті ви, швидше за все, отримали декілька учасників. (А якщо ні, то ви будуєте його самостійно протягом тривалого часу, що по суті те саме. Спробуйте прочитати код, який ви написали 3 роки тому, якщо ви мені не вірите!) Це означає, що ви не знаєте, що було перебираючи голову людини, яка написала майже будь-яку задану частину коду в той час, коли вони її написали, тому що вас там не було, або не пам’ятаєте, чи був це ваш власний код давно. Наявність декларацій типу справді допомагає зрозуміти, у чому полягав намір коду!

Такі люди, як хлопець у цитаті, часто неправильно характеризують переваги статичного набору тексту як про "допомогу компілятору" або "про ефективність" у світі, де майже необмежені апаратні ресурси роблять це все менш актуальним з кожним роком. Але, як я показав, хоча ці переваги, безумовно, існують, головна користь полягає в людських факторах, зокрема, читабельності коду та ремонтопридатності. (Хоча додаткова ефективність, безумовно, приємний бонус!)


24
"Цей хлопець є троллем". - Я не впевнений, що атака ad hominem допоможе вашому інакше добре представленому випадку. І хоча я добре усвідомлюю, що аргумент влади є настільки ж поганою помилкою, як і ad hominem, я все ж хотів би зазначити, що Гілад Брача, мабуть, розробив більше мов і (найбільш актуальних для цієї дискусії) систем статичного типу, ніж більшість. Лише невеликий уривок: він є єдиним дизайнером Newspeak, співавтором Dart, співавтором специфікації мови Java та специфікації віртуальної машини Java, працював над розробкою Java та JVM, розробленим…
Jörg W Mittag

10
Strongtalk (система статичного типу для Smalltalk), система типу Dart, система типу Newspeak, його кандидатська дисертація про модульність є основою майже кожної сучасної модульної системи (наприклад, Java 9, ECMAScript 2015, Scala, Dart, Newspeak, Ioke , Seph), його статті про міксин зробили революцію в тому, як ми думаємо про них. Тепер, це НЕ означає , що він має рацію, але я дійсно думаю , що, розробивши кілька систем статичного типу робить його трохи більше , ніж «тролів».
Йорг W Міттаг

17
"Хоча це правда, що системи типів" обмежують вас підмножиною ", те, що знаходиться поза цією підмножиною, за визначенням - це матеріал, який не працює". - Це неправильно. Ми знаємо з нерозбірливості проблеми зупинки, теореми Райса та безлічі інших результатів нерозбірливості та необчислюваності, що перевіряючий статичний тип не може вирішити для всіх програм, чи є вони безпечними для типу чи не небезпечними для типу. Він не може прийняти ті програми (деякі з яких небезпечні для типу), тому єдиним розумним вибором є їх відхилення (однак, деякі з них є безпечними для типу). Як варіант, мова повинна бути розроблена в…
Jörg W Mittag

9
… Таким чином, що унеможливить програміст написати ті програми, які не можна визначити, але знову ж таки, деякі з них насправді є безпечними для типу. Отже, незалежно від того, як ви його нарізаєте: програмісту заборонено писати безпечні програми. І оскільки насправді їх (як правило) існує нескінченно багато, ми можемо бути майже впевнені, що принаймні деякі з них - це не лише речі, які справді працюють, але й корисні.
Йорг W Міттаг

8
@MasonWheeler: Проблема зупинки виникає постійно, саме в контексті перевірки статичного типу. Якщо мови ретельно не розроблені, щоб перешкодити програмісту писати певні види програм, статична перевірка типу швидко стає еквівалентною вирішенню проблеми зупинки. Або ви в кінцевому підсумку з програмами ви не можете писати , тому що вони можуть заплутати перевірки типів, або ви в кінцевому підсумку з програмами ви які дозволяється писати , але вони приймають нескінченну кількість часу , щоб перевірки типу.
Йорг W Міттаг

27

Я переходжу до «шаблону» частини, оскільки я думаю, що вона переходить у визначення того, що є, чи не є, і я давно втратив інтерес до цієї дискусії. Що я скажу - це те, що ти можеш робити на деяких мовах, чого не можеш робити в інших. Дозвольте мені зрозуміти, я не кажу, що є проблеми, які ви можете вирішити однією мовою, які ви не можете вирішити на іншій. Мейсон вже вказував на завершеність Тьюрінга.

Наприклад, я написав клас в python, який приймає обгортання XML-елемента DOM і перетворює його в об'єкт першого класу. Тобто, ви можете написати код:

doc.header.status.text()

і у вас є вміст цього шляху від проаналізованого XML-об'єкта. вид акуратний і охайний, ІМО. І якщо немає головного вузла, він просто повертає манекенні об’єкти, які не містять нічого, крім манекенових об’єктів (черепах до кінця вниз.) Немає реального способу зробити це, скажімо, на Java. Вам слід було б скласти клас заздалегідь, який ґрунтується на певних знаннях структури XML. Якщо відмінити, чи це гарна ідея, подібні речі насправді змінюють спосіб вирішення проблем динамічною мовою. Я не кажу, що це змінюється таким чином, що завжди завжди краще. Динамічні підходи мають певні витрати, і відповідь Мейсона дає гідний огляд. Чи є вони гарним вибором, залежить від багатьох факторів.

З іншого боку, це можна зробити на Java, оскільки ви можете побудувати інтерпретатор python на Java . Той факт, що вирішення конкретної проблеми даною мовою може означати побудову перекладача чи щось подібне до нього, часто ігнорується, коли люди говорять про повноту Тьюрінга.


4
На Java це зробити не можна, оскільки Java погано розроблена. Це не буде настільки важко при використанні C # IDynamicMetaObjectProvider, а в Boo просто мертво. ( Ось реалізація менш ніж 100 рядків, входить до складу стандартного вихідного дерева на GitHub, тому що це так просто!)
Мейсон Wheeler

6
@MasonWheeler "IDynamicMetaObjectProvider"? Це пов’язано з dynamicключовим словом C # ? ... що ефективно просто вкладається в динамічне введення тексту на C #? Не впевнений, що ваш аргумент справедливий, якщо я правий.
jpmc26

9
@MasonWheeler Ви вступаєте в семантику. Не вступаючи в суперечки про деталі (тут ми не розвиваємо математичний формалізм щодо СЕ.), Динамічне введення тексту - це практика попередніх компіляційних рішень щодо часу навколо типів, особливо перевірка того, що кожен тип має конкретних членів, до яких програма має доступ. Це мета, яка dynamicдосягається в C #. "Роздуми та пошук словника" трапляються під час виконання, а не час компіляції. Я дійсно не впевнений, як можна зробити випадок, коли це не додає динамічного введення тексту мові. Моя думка, що останній абзац Джиммі охоплює це.
jpmc26

44
Незважаючи на те, що я не є великим шанувальником Java, я також наважуюся сказати, що називати Яву "погано розробленою" спеціально, тому що вона не додала динамічного набору тексту ... надмірно.
jpmc26

5
Окрім трохи зручнішого синтаксису, чим це відрізняється від словника?
Теодорос Чатзіґянакікіс

10

Цитата правильна, але також справді недоброзичлива. Розберемо це, щоб зрозуміти, чому:

Дивовижна річ щодо динамічного набору тексту - це дозволяє висловити все, що можна обчислити.

Ну, не зовсім. Мова з динамічною типізацією дозволяє висловити все , як довго , як це Тьюринг , який більшість з них. Сама система типу не дає вам все висловити. Давайте ж дамо йому користь від сумнівів.

А системи типів не типу типів, як правило, можна вирішити, і вони обмежують вас підмножиною.

Це правда, але зауважте, що зараз ми наполегливо говоримо про те, що дозволяє система типів , а не те, що дозволяє мова, яка використовує типну систему. Незважаючи на те, що можна використовувати систему типів для обчислення матеріалів під час компіляції, це, як правило, не є Тюрінгом повним (оскільки система типів, як правило, можна вирішити), але майже будь-яка статично набрана мова також є Тюрінг повною в процесі виконання (залежно введені мови є ні, але я не вірю, що ми говоримо про них тут).

Люди, які віддають перевагу системам статичного типу, кажуть: «це добре, це досить добре; всі цікаві програми, які ви хочете написати, працюватимуть як типи ». Але це смішно - раз у вас є система типу, ви навіть не знаєте, які цікаві програми є.

Проблема полягає в тому, що мови динамічного типу мають статичний тип. Іноді все є рядком, і частіше існує якийсь тегований союз, де кожна річ є або мішком властивостей, або значенням, як int чи double. Проблема полягає в тому, що статичні мови можуть це робити також. Історично це було дещо незграбніше, але сучасні статично набрані мови роблять це майже так само просто, як використання мови динамічного типу, тож як може бути різниця в що програміст може бачити як цікаву програму? Статичні мови мають точно такі ж мітки, як і інші типи.

Щоб відповісти на запитання в заголовку: Ні, немає моделей дизайну, які неможливо реалізувати статично набраною мовою, оскільки ви завжди можете реалізувати достатню кількість динамічної системи для їх отримання. Можливо, у динамічній мові ви знайдете шаблони, які ви отримуєте за "безкоштовно"; це може бути, а може і не варто міритися з недоліками цих мов для YMMV .


2
Я не зовсім впевнений, ви просто відповіли "так" чи "ні". Звучить більше як ні.
користувач7610

1
@TheodorosChatzigiannakis Так, як ще можна було б реалізувати динамічні мови? По-перше, ви здастеся архітектору-космонавту, якщо вам захочеться трохи впровадити систему динамічного класу чи щось інше. По-друге, напевно, у вас немає ресурсу, щоб зробити його налагоджуваним, повністю інтроспективним, виконавським ("просто використовувати словник" - це те, як реалізуються повільні мови). По-третє, деякі динамічні функції найкраще використовувати при інтеграції в цілу мову, а не лише як бібліотеку: наприклад, подумайте про збирання сміття ( є ГК як бібліотеки, але вони не використовуються зазвичай).
coredump

1
@ Теодорос Згідно з документом, який я вже тут один раз пов'язував, усі, крім 2,5% структур (у модулях Python, які розглянули дослідження), можуть бути легко виражені набраною мовою. Можливо, 2,5% робить оплату витрат на динамічне набір тексту варто. Це, по суті, про моє запитання. neverworkintheory.org/2016/06/13/polymorphism-in-python.html
користувач7610

3
@JiriDanek Наскільки я можу сказати, ніщо не заважає статично набраній мові мати поліморфні плями виклику та підтримувати статичне введення тексту в процесі. Див. Статичну перевірку типу мульти-методів . Можливо, я нерозумію ваше посилання.
Теодорос Чатзіґананакіс

1
«Мова з динамічною типізацією дозволяє висловити все до тих пір , як це Тьюринг, який більшість з них.» Хоча це, звичайно , вірного твердження, це не реально тримати в «реальному світі» , тому що кількість тексту один має писати могло б бути надзвичайно великим.
Даніель Жур

4

Звичайно, ви можете робити лише динамічно набрані мови. Але вони не обов'язково будуть хорошим дизайном.

Ви можете призначити спочатку ціле число 5, а потім рядок 'five'або Catоб'єкт тій самій змінній. Але ви лише ускладните читачеві свого коду зрозуміти, що відбувається, яка мета кожної змінної.

Ви можете додати новий метод до бібліотечного класу Ruby та отримати доступ до його приватних полів. Можуть бути випадки, коли такий злом може бути корисним, але це буде порушенням інкапсуляції. (Я не проти додавання методів, які покладаються лише на загальнодоступний інтерфейс, але це статично набрані методи розширення C # не можуть.)

Ви можете додати нове поле до об’єкта чужого класу, щоб передати деякі додаткові дані навколо нього. Але краще дизайн просто створити нову структуру або розширити оригінальний тип.

Як правило, чим організованіше ви хочете, щоб ваш код залишався, тим менше переваги вам слід скористатись тим, що зможете динамічно змінювати визначення типів або призначати значення різних типів одній змінній. Але тоді ваш код нічим не відрізняється від того, що ви могли досягти статично набраною мовою.

Які динамічні мови гарні - це синтаксичний цукор. Наприклад, читаючи десеріалізований об’єкт JSON, ви можете посилатися на вкладене значення просто як obj.data.article[0].content- набагато акуратніше, ніж скажімо obj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content").

Особливо Ruby-розробники могли б довго розмовляти про магію, якої можна досягти, реалізуючи method_missing, що є методом, що дозволяє обробляти спроби викликів до недекларованих методів. Наприклад, ActiveRecord ORM використовує його, щоб ви могли здійснити виклик User.find_by_email('joe@example.com')без жодного find_by_emailспособу оголошення . Звичайно, це нічого, що неможливо було б досягти як UserRepository.FindBy("email", "joe@example.com")статично набраною мовою, але ви не можете заперечувати його акуратність.


4
Напевно є речі, які можна робити лише на мовах, що мають статичний вклад. Але вони не обов'язково будуть хорошим дизайном.
coredump

2
Справа про синтаксичний цукор має дуже мало спільного з динамічним набором тексту і все з, ну, синтаксисом.
близько

@leftaroundabout Шаблони пов'язані з синтаксисом. Системи типів також мають багато спільного з цим.
користувач253751

4

Шаблон Dynamic Proxy - це ярлик для реалізації об'єктів проксі, не потрібен один клас на тип, який потрібно проксі.

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

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

Використовуючи це, Proxy(someObject)створюється новий об’єкт, який веде себе так само, як someObject. Очевидно, ви також хочете якось додати додаткову функціональність, але це корисна база для початку. Повною статичною мовою вам потрібно буде написати один клас проксі для кожного типу, який ви хочете проксі, або використовувати динамічне генерування коду (який, правда, включений до стандартної бібліотеки багатьох статичних мов, значною мірою тому, що їх дизайнери знають про проблеми, не в змозі це зробити).

Інший випадок використання динамічних мов - це так зване "мавпання". Багато в чому це анти-візерунок, а не візерунок, але його можна використовувати корисними способами, якщо це робити обережно. І хоча немає теоретичних причин, коли патч мавп не міг бути реалізований статичною мовою, я ніколи не бачив жодної, яка насправді його має.


Я думаю, що я міг би наслідувати це в Go. Існує набір методів, якими повинні володіти всі проксі-об'єкти (інакше качка може не збиватися, і всі розпадаються). Я можу створити інтерфейс Go за допомогою цих методів. Мені доведеться подумати над цим більше, але я думаю, те, що я маю на увазі, спрацює.
user7610

Ви можете зробити щось подібне на будь-якій мові .NET з RealProxy та generics.
LittleEwok

@LittleEwok - RealProxy використовує генерацію коду виконання - як я кажу, у багатьох сучасних статичних мовах є такий спосіб вирішення, але все ж простіше в динамічній мові.
Жуль

Методи розширення C # начебто схожі на маніпулювання. Ви не можете змінити існуючі методи, але ви можете додати нові.
Ендрю каже, що відновити Моніку

3

Так , існує багато моделей і прийомів, які можливі лише на динамічно набраній мові.

Виправлення мавп - це техніка, коли властивості або методи додаються до об'єктів або класів під час виконання. Ця методика неможлива на мові статичного типу, оскільки це означає, що типи та операції неможливо перевірити під час компіляції. Або кажучи іншим способом, якщо мова підтримує патч-мавпу, це за визначенням динамічна мова.

Можна довести, що якщо мова підтримує виправлення мавп (або подібні методи для зміни типів під час виконання), її неможливо перевірити статично. Тож це не просто обмеження в існуючих на даний момент мовах, це фундаментальне обмеження статичного набору тексту.

Тож цитата, безумовно, правильна - більше можливостей у динамічній мові, ніж у мові, що набирається статично. З іншого боку, певні види аналізу можливі лише на мові, що має статичний тип. Наприклад, ви завжди знаєте, які операції дозволені для даного типу, що дозволяє виявляти незаконні операції за типом компіляції. Немає такої перевірки на динамічній мові, коли операції можна додавати або видаляти під час виконання.

Ось чому немає очевидних "найкращих" у конфлікті між статичною та динамічною мовами. Статичні мови відмовляються від певної потужності під час виконання в обмін на інший тип живлення під час компіляції, що, на їхню думку, зменшує кількість помилок і полегшує розвиток. Одні вважають, що компроміс того вартий, інші - ні.

Інші відповіді стверджують, що еквівалентність Тьюрінга означає, що все можливе на одній мові можливо на всіх мовах. Але цього не випливає. Щоб підтримати щось на зразок виправлення мавп в статичній мові, вам в основному потрібно реалізувати динамічну підмову всередині статичної мови. Це, звичайно, можливо, але я б заперечував, що ви потім програмуєте вбудовану динамічну мову, оскільки ви також втрачаєте перевірку статичного типу, що існує в мові хосту.

C # з версії 4 підтримують динамічно набрані об'єкти. Очевидно, що мовні дизайнери бачать користь у тому, що вони мають доступ до обох типів друку. Але це також показує, що ви не можете мати торт і їсти я теж: Коли ви використовуєте динамічні об'єкти в C #, ви отримуєте можливість робити щось на зразок патч-мавп, але ви також втрачаєте статичну перевірку типу взаємодії з цими об'єктами.


+1 ваш другий до останнього абзацу, я думаю, що це вирішальний аргумент Я б все-таки стверджував, що є різниця, хоча, як і у статичних типів, ви маєте повний контроль над тим, де і чим можна виправити мавпу
jk.

2

Цікаво, чи існують корисні схеми дизайну чи стратегії, які, використовуючи формулювання цитати, «не працюють як типи»?

Так і ні.

Бувають ситуації, коли програміст знає тип змінної з більшою точністю, ніж компілятор. Компілятор може знати, що щось є Об'єктом, але програміст буде знати (завдяки інваріантам програми), що це насправді String.

Дозвольте мені показати кілька прикладів цього:

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

Я знаю, що someMap.get(T.class)поверне a Function<T, String>, через те, як я сконструював деяку карту. Але Java впевнений, що у мене є функція.

Інший приклад:

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

Я знаю, що data.properties.rowCount буде дійсним посиланням та цілим числом, тому що я перевірив дані щодо схеми. Якби це поле не було, виняток був би кинутий. Але компілятор знав би лише те, що або кидає виняток, або повертає якусь загальну JSONValue.

Інший приклад:

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

"II6s" визначає спосіб кодування даних трьох змінних. Оскільки я вказав формат, я знаю, які типи будуть повертатися. Компілятор знав би лише, що він повертає кортеж.

Об'єднуючою темою всіх цих прикладів є те, що програміст знає тип, але система типу Java не зможе це відобразити. Компілятор не знатиме типів, і тому статично набрана мова не дозволить мені їх називати, тоді як динамічно набрана мова буде.

Ось у чому оригінальна цитата, яку вона отримує:

Дивовижна річ щодо динамічного набору тексту - це дозволяє висловити все, що можна обчислити. А системи типів не типу типів, як правило, можна вирішити, і вони обмежують вас підмножиною.

Під час використання динамічного набору тексту я можу використовувати найбільш похідний тип, про який я знаю, а не просто найпохідніший тип, про який знає моя система типу. У всіх вищевикладених випадках у мене є семантично правильний код, але його буде відхилено статичною системою набору тексту.

Однак, щоб повернутися до свого питання:

Цікаво, чи існують корисні схеми дизайну чи стратегії, які, використовуючи формулювання цитати, «не працюють як типи»?

Будь-який із наведених вище прикладів, і справді будь-який приклад динамічного набору тексту може бути дійсним при статичному введенні, додавши відповідні касти. Якщо ви знаєте тип, який ваш компілятор не робить, просто скажіть компілятору, перерахувавши значення. Тож на певному рівні ви не збираєтеся отримувати додаткові шаблони, використовуючи динамічне введення тексту. Вам просто знадобиться зробити більше, щоб працювати статично введений код.

Перевага динамічного введення тексту полягає в тому, що ви можете просто використовувати ці зразки, не замислюючись про те, що його складно переконати систему типу в їх валідності. Це не змінює наявні шаблони, просто можливо спрощує їх реалізацію, оскільки вам не доведеться розбирати, як змусити вашу систему типів розпізнавати шаблон або додавати касти для підриву системи типів.


1
Чому java - це відрізаний момент, коли ви не повинні переходити до "більш просунутої / складної системи типу"?
jk.

2
@jk, що приводить тебе до думки, що це я говорю? Я явно уникав брати сторону щодо того, чи варто досконалішої / складнішої системи варто.
Вінстон Еверт

2
Деякі з них є жахливими прикладами, а інші, здається, є скоріше мовними рішеннями, а не типізованими проти не набраними. Мене особливо бентежить питання, чому люди вважають, що десеріалізація є такою складною мовою набору. Введений результат був би, data = parseJSON<SomeSchema>(someJson); print(data.properties.rowCount); і якщо у вас немає класу, який слід дезаріалізувати, ми можемо повернутися до data = parseJSON(someJson); print(data["properties.rowCount"]);- який все ще набирається і виражає той самий намір.
NPSF3000

2
@ NPSF3000, як працює функція parseJSON? Здавалося б, або використовувати відображення або макроси. Як дані ["properties.rowCount"] можна вводити статичною мовою? Як можна було знати, що отримане значення є цілим числом?
Вінстон Еверт

2
@ NPSF3000, як ви плануєте використовувати його, якщо ви не знаєте його цілого числа? Як ви плануєте перебирати елементи в списку в JSON, не знаючи, що це масив? Суть мого прикладу полягала в тому, що я знав, що data.propertiesце об'єкт, і я знав, що data.properties.rowCountце ціле число, і я міг просто написати код, який їх використовував. Ваша запропонована пропозиція data["properties.rowCount"]не забезпечує те саме.
Вінстон Еверт

1

Ось кілька прикладів з Objective-C (динамічно набраних), які неможливі в C ++ (статично набрані):

  • Поміщення об'єктів декількох різних класів в один контейнер.
    Звичайно, для цього потрібна перевірка типу виконання для подальшої інтерпретації вмісту контейнера, і більшість друзів статичного набору тексту заперечують проти того, що вам не слід це робити в першу чергу. Але я виявив, що поза релігійними дискусіями це може стати в нагоді.

  • Розширення класу без підкласу.
    У Objective-C ви можете визначити нові функції членів для існуючих класів, включаючи такі, що визначаються мовою NSString. Наприклад, ви можете додати метод stripPrefixIfPresent:, щоб ви могли сказати [@"foo/bar/baz" stripPrefixIfPresent:@"foo/"](зверніть увагу на використання NSSringлітералів @"").

  • Використання об'єктно-орієнтованих зворотних викликів.
    У статично типових мовах, таких як Java та C ++, ви повинні пройти значну довжину, щоб бібліотека могла викликати довільний член об'єкту, що надається користувачем. У Java обхідне рішення - це пара інтерфейсів / адаптерів плюс анонімний клас, у C ++ спосіб вирішення - це зазвичай шаблон, який означає, що код бібліотеки повинен піддаватися коду користувача. У Objective-C ви просто передаєте посилання на об'єкт плюс селектор методу в бібліотеку, і бібліотека може просто і безпосередньо викликати зворотний виклик.


Я можу зробити перше в C ++, подавши на пустоту *, але це обходить систему типів, тому вона не рахується. Я можу зробити друге в C # за допомогою методів розширення, ідеально всередині системи типів. По-третє, я думаю, що "селектор для методу" може бути лямбда, тому будь-яка статично набрана мова з лямбдами може зробити те саме, якщо я правильно розумію. Я не знайомий з ObjC.
користувач7610

1
@JiriDanek "Я можу зробити перше в C ++ шляхом передачі до void *", не зовсім так, що код, який читає елементи, не може самостійно отримати фактичний тип. Вам потрібні теги типу. Крім того, я не думаю, що вислів "Я можу це зробити на <мові>" є відповідним / продуктивним способом поглянути на це, тому що ти завжди можеш імітувати їх. Важливим є виграш в експресивності та складності реалізації. Крім того, вам здається, що якщо мова має як статичні, так і динамічні можливості (Java, C #), вона належить виключно до "статичної" сім'ї мов.
coredump

1
@JiriDanek void*поодинці не є динамічним набором тексту, це відсутність набору тексту. Але так, динамічні_cast, віртуальні таблиці тощо роблять C ++ не чисто статично набраним. Це погано?
coredump

1
Це говорить про те, що корисно мати можливість підривати систему типів при необхідності. Маючи евакуаційний люк, коли вам це потрібно. Або хтось вважав це корисним. Інакше вони б не поклали його на мову.
користувач7610

2
@JiriDanek Я думаю, ти досить сильно прибив це своїм останнім коментарем. Ці евакуаційні люки можуть бути надзвичайно корисними, якщо використовувати їх обережно. Тим не менш, з великою владою наступає велика відповідальність, і багато людей, які її зловживають ... Таким чином, відчувати себе набагато краще використовувати вказівник на загальний базовий клас, з якого всі інші класи виводяться за визначенням (як це буває) і в Objective-C, і в Java), і покладатися на RTTI, щоб розказати випадки, ніж надати void*певний тип об'єкта. Перший видає помилку виконання, якщо ви зіпсуєте, пізніше призводить до невизначеної поведінки.
cmaster
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.