Pythonのような動的に型付けされた言語でのみ可能なデザインパターンはありますか?


30

関連する質問を読みました。Pythonのような動的言語には不要なデザインパターンはありますか?Wikiquote.orgでこの引用を思い出した

動的型付けのすばらしいところは、計算可能なものをすべて表現できることです。また、型システムはそうではありません—型システムは通常決定可能であり、サブセットに制限されます。静的型システムを好む人は、「それでいい、それで十分だ。作成したいすべての興味深いプログラムが型として機能します。」しかし、それはばかげています。型システムができたら、どんな面白いプログラムがあるのか​​さえわかりません。

---ソフトウェアエンジニアリングラジオエピソード140:Gilad BrachaによるNewspeakおよびPluggable Types

引用の定式化を使用して、「型として機能しない」有用な設計パターンまたは戦略があるのだろうか?


3
ダブルディスパッチと訪問者パターンは、静的に型付けされた言語では達成が非常に難しいが、動的言語では容易に達成できることがわかりました。例えばこの回答(質問)を参照してください:programmers.stackexchange.com/a/288153/122079
user3002473

7
もちろん。たとえば、実行時に新しいクラスを作成するパターン。(これはJavaでも可能ですが、C ++ではできません。動的なスケールがあります)。
user253751

1
それはあなたの型システムがどれだけ洗練されているかに大きく依存するだろう:-)関数型言語は通常これで非常にうまくいく。
ベルギ

1
誰もがHaskellやOCamlではなく、JavaやC#のような型システムを話しているようです。強力な型システムを備えた言語は、動的言語と同じくらい簡潔にできますが、型の安全性は保ちます。
アンドリューは、Reinstate Monica

@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に特化したすべてのメソッド?これは内部化されたデータです。タイプは、これのもう1つの側面にすぎません。


(参照:CFFI


39

短い答え:いいえ、チューリングの等価性のため。

長い答え:この男はトロールです。型システムが「サブセットに限定する」のは事実ですが、そのサブセットの外側のものは、定義上、機能しないものです。

チューリング完全なプログラミング言語(汎用プログラミング用に設計された言語であり、そうでないものも多数あります。クリアするのは非常に低く、チューリングになるシステムの例がいくつかあります。意図せずに完了)他のチューリング完全プログラミング言語で実行できます。これは「チューリング等価」と呼ばれ、正確に言うことを意味するだけです。重要なのは、他の言語でも同じように簡単に他のことを行えるということではありません。そもそも新しいプログラミング言語を作成することの第一の目的だと主張する人もいます。既存の言語が吸い込むもの。

たとえば、すべての変数、パラメーター、および戻り値を基本Object型として宣言し、リフレクションを使用して内部の特定のデータにアクセスすることで、動的な型システムを静的なOO型システムの上でエミュレートできます。静的言語ではできない動的言語でできることは文字通り何もないことがわかります。しかし、そのようにするのはもちろん大きな混乱です。

引用からの男は、静的型があなたができることを制限することは正しいが、それは重要な機能であり、問​​題ではない。道路上の線は、あなたの車でできることを制限していますが、それらを制限したり、役立つと思いますか?(車が反対方向に進み、運転している場所に来ないように指示するものが何もない、混雑した複雑な道路を運転したくないことを知っています!)明確に線引きするルールを設定することにより無効な動作と見なされ、それが発生しないことを確認すると、厄介なクラッシュが発生する可能性が大幅に減少します。

また、彼は反対側の特性を誤っています。「書きたいすべての興味深いプログラムが型として機能する」ということではなく、「書きたいすべての興味深いプログラム型を必要する」ということです。特定のレベルの複雑さを超えると、2つの理由から、型システムなしでコードベースを維持することが非常に難しくなります。

まず、型注釈のないコードは読みにくいためです。次の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。!の定義を調べてください。さらに、同じ名前のプロパティを持つ互換性のない型を渡したために不正な動作が発生する可能性が、ゼロになります。これは、型システムがその間違いを許さないためです。

2番目の理由は最初の理由に基づいています。大規模で複雑なプロジェクトでは、多くの場合、複数の貢献者がいます。(そうでない場合、あなたは長い間自分でそれを構築しています。これは本質的に同じです。私を信じないなら、3年前に書いたコードを読んでみてください!)これは、何がわからなかったかを意味しますあなたがそこにいなかった、またはずっと前にあなた自身のコードだったかどうか覚えていないので、彼らがそれを書いた時にコードのほとんどすべての与えられた部分を書いた人の頭を通り抜けます。型宣言があると、コードの意図が何であるかを理解するのに役立ちます!

引用の男のような人は、ほとんど無制限のハードウェアリソースが年々重要性を低下させている世界では、静的型付けの利点を「コンパイラを支援する」または「効率について」と誤解することがよくあります。しかし、私が示したように、これらの利点は確かに存在しますが、主な利点は人的要因、特にコードの可読性と保守性です。(追加された効率は確かに素晴らしいボーナスです!)


24
「この男はトロルです。」–アドホミネム攻撃が、それ以外の場合によく提示されたケースを助けるかどうかはわかりません。そして、私は権威からの議論がアドホミネムと同様に悪い誤thatであることをよく知っていますが、Gilad Brachaはおそらくより多くの言語と(この議論に最も関連する)より多くの静的型システムを設計したことを指摘したいと思います。ただ、小さな抜粋:彼はニュースピーク、ダーツ、Java言語仕様と設計されたJavaとJVMの設計に取り組んJava仮想マシン仕様の共著者の共同デザイナーの唯一のデザイナーである...
イェルクWミッターク

10
Strongtalk(Smalltalkの静的型システム)、Dart型システム、Newspeak型システム、モジュール性に関する博士論文は、ほとんどすべての最新のモジュールシステム(Java 9、ECMAScript 2015、Scala、Dart、Newspeak、Iokeなど)の基礎です、Seph)、ミックスインに関する彼の論文は、私たちがそれらについて考える方法に革命をもたらしました。さて、それは彼が正しいことを意味するわけではありませが、複数の静的型システムを設計したことで、彼は「トロール」以上のことになると思います。
ヨルグWミットタグ

17
「型システムが「サブセットに限定する」ことは事実ですが、そのサブセットの外側の要素は、定義上、機能しないものです。」- これは間違っています。停止問題の決定不能性、ライスの定理、およびその他の無決定性および計算不能性の結果から、静的型チェッカーはすべてのプログラムについて、タイプセーフかタイプセーフでないかを判断できないことがわかります。これらのプログラム(一部はタイプセーフではない)を受け入れることができないため、唯一の正しい選択はそれらを拒否することです(ただし、一部はタイプセーフです)。また、言語は...で設計する必要がある
イェルクWミッターク

9
…プログラマーがこれらの決定できないプログラムを書くことを不可能にするような方法ですが、それらのいくつかは実際にタイプセーフです。そのため、どのようにスライスしても、プログラマはタイプセーフなプログラムを書くことができません。それらの多くは(通常は)無限に実際に存在しているので、私たちはそれらの少なくとも一部がものだけではないことはほぼ確実であることができない仕事をするだけでなく、便利。
ヨルグWミットタグ

8
@MasonWheeler:静的型チェックのコンテキストで、停止問題が常に発生します。プログラマーが特定の種類のプログラムを作成できないように言語が慎重に設計されていない限り、静的型チェックはすぐに停止問題の解決と同等になります。型チェッカーを混乱させる可能性があるため、書き込みを許可されていないプログラムになるか、または書き込み許可されているが型チェックに無限の時間がかかるプログラムになります。
ヨルグWミットタグ

27

私は「パターン」の部分を回避します。なぜなら、それはパターンであるかどうかの定義に委ねられ、私はその議論への関心を長く失ってしまったからです。私が言うことは、他の言語ではできないいくつかの言語でできることがあるということです。はっきりさせてください。ある言語では解決できない問題が、別の言語では解決できない問題があるとは言っていません。メイソンはすでにチューリングの完全性を指摘しています。

たとえば、XML DOM要素をラップしてファーストクラスオブジェクトにするPythonでクラスを作成しました。つまり、次のコードを記述できます。

doc.header.status.text()

そして、解析されたXMLオブジェクトからそのパスのコンテンツを取得します。きちんとしていて、IMO。また、ヘッドノードがない場合は、ダミーオブジェクトのみを含むダミーオブジェクトを返します(カメはずっと下にあります)。たとえば、Javaでそれを行う実際の方法はありません。XMLの構造に関する知識に基づいてクラスを事前にコンパイルしておく必要があります。これが良いアイデアであるかどうかは別として、この種のことは動的言語の問題を解決する方法を実際に変えます。ただし、必ずしも常に優れているとは限りません。動的なアプローチにはいくつかの明確なコストがあり、メイソンの答えはまともな概要を提供します。それらが適切な選択であるかどうかは、多くの要因に依存します。

サイドノートでは、あなたがすることができ、あなたが構築することができますので、Javaでこれを行うJavaでPythonインタプリタを。特定の言語で特定の問題を解決するということは、通訳やそれに似たものを構築することを意味する可能性があるという事実は、チューリングの完全性について話すときにしばしば見過ごされます。


4
Javaの設計が不十分なため、Javaでこれを行うことはできません。C#ではを使用するのはそれほど難しくなくIDynamicMetaObjectProvider、Booでは非常に簡単です。(これは100行未満の実装で、GitHubの標準ソースツリーの一部として含まれています。簡単だからです!)
Mason Wheeler

6
@MasonWheeler "IDynamicMetaObjectProvider"?それはC#のdynamicキーワードに関連していますか?...これは、C#への動的な型付けに効果的に取り組むだけですか?私が正しいかどうかあなたの議論が有効かどうかわからない。
jpmc26

9
@MasonWheelerあなたはセマンティクスを取得しています。特徴について議論することなく(ここではSEの数学的形式を開発していません)、動的型付けは、型に関するコンパイル時の決定を先に行う慣習です。特に、各型にプログラムがアクセスする特定のメンバーがあることの検証です。それがdynamicC#で達成する目標です。「反射と辞書の検​​索」は、コンパイル時ではなく実行時に行われます。動的型付けが言語に追加されないというケースをどのように作成できるか、私には本当にわかりません。私のポイントは、ジミーの最後の段落がそれをカバーしていることです。
jpmc26

44
ジャワの大ファンされていないにも関わらず、私はまた、呼び出し元のJavaは「設計が不十分な」と言うあえて、具体的には、動的型付けがある...熱心追加しませんでしたので。
jpmc26

5
少し便利な構文は別として、これは辞書とどう違うのですか?
セオドロスチャツィギアンナキス

10

引用は正しいが、実に不誠実でもある。理由を見てみましょう:

動的型付けのすばらしいところは、計算可能なものをすべて表現できることです。

まあ、そうではありません。動的型付けを備えた言語では、チューリング完全である限り、何でも表現できます。型システム自体では、すべてを表現することはできません。ここで彼に疑いの恩恵を与えましょう。

また、型システムはそうではありません—型システムは通常決定可能であり、サブセットに制限されます。

これは事実ですが、型システムを使用する言語が許可するものではなく、型システムが許可するものについてしっかりと話していることに注意してください。型システムを使用してコンパイル時にものを計算することは可能ですが、これは一般的にチューリング完全ではありません(型システムは一般に決定可能です)が、ほとんどすべての静的型付け言語もランタイムでチューリング完全です(依存型付け言語はそうではありませんが、ここでそれらについて話しているとは思いません)。

静的型システムを好む人は、「それでいい、それで十分だ。作成したいすべての興味深いプログラムが型として機能します。」しかし、それはばかげています。型システムができたら、どんな面白いプログラムがあるのか​​さえわかりません。

問題は、動的型言語には静的型があるということです。時にはすべてが文字列であり、より一般的には、すべてのものがプロパティのバッグまたはintやdoubleのような値であるタグ付きユニオンがあります。問題は、静的言語でもこれができることです。歴史的には、これはやや不格好でしたが、現代の静的型付け言語では、動的型言語を使用するのと同じくらい簡単にこれを行うことができます。プログラマーが面白いプログラムとして見ることができるものは何ですか?静的言語には、他のタイプとまったく同じタグ付きユニオンがあります。

タイトルの質問に答えるには:いいえ、静的型付け言語では実装できない設計パターンはありません。それを取得するのに十分な動的システムを常に実装できるからです。動的言語では「無料」で取得できるパターンがある場合があります。これは、YMMVのこれらの言語の欠点に耐える価値があるかもしれません。


2
あなたがイエスかノーと答えただけなのか、完全にはわかりません。私にはノーのように聞こえます。
user7610

1
@TheodorosChatzigiannakisはい、動的言語は他にどのように実装されますか?最初に、動的なクラスシステムまたは他の少し複雑なシステムを実装する場合は、宇宙飛行士の設計者に渡します。第二に、おそらくデバッグ可能、完全にイントロスペクト可能、パフォーマンスを向上させるためのリソースがありません(「辞書を使用するだけ」が遅い言語の実装方法です)。だけではなく、ライブラリとして、全言語に統合されたときに第三には、いくつかの動的な機能が最良に使用されます。たとえば、ガベージコレクションを考えて(そこにあるライブラリなどのGCは、彼らは一般的に使用されていません)。
コアダンプ

1
@Theodorosすでに一度リンクした論文によると、2.5%を除くすべての構造(Pythonモジュールでの研究で見た)は、型付き言語で簡単に表現できます。たぶん、2.5%は動的タイピングのコストを払う価値があるでしょう。それが本質的に私の質問についてでした。neverworkintheory.org/2016/06/13/polymorphism-in-python.html
user7610

3
@JiriDanek私の知る限り、静的に型付けされた言語がポリモーフィックなコールスポットを持ち、プロセスで静的な型付けを維持することを妨げるものはありません。マルチメソッドの静的型チェックを参照してください。あなたのリンクを誤解しているのかもしれません。
セオドロスチャツィジアンナキス

1
「それはほとんどがである、チューリング完全だと動的な型付けを持つ言語は、あなたが長いとして何かを表現することができます。」これはもちろん真の文であるが、それはしない、本当にため、「現実の世界」に保持するのテキスト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")、静的に型付けされた言語のように達成できなかったものは何もありませんが、その清nさを否定することはできません。


4
静的に型付けされた言語でしかできないことは確かです。しかし、それらは必ずしも良いデザインではありません。
コアダンプ

2
構文糖についてのポイントは、動的型付けや、構文に関するすべてとはほとんど関係ありません。

@leftaroundaboutパターンは構文に関係しています。型システムもそれと関係があります。
user253751

4

動的プロキシパターンは、プロキシする必要のあるタイプごとに1つのクラスを必要とせずに、プロキシオブジェクトを実装するためのショートカットです。

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

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

これを使用して、Proxy(someObject)と同じ動作をする新しいオブジェクトを作成しますsomeObject。もちろん、何らかの方法で追加の機能を追加することもできますが、これは開始するのに便利なベースです。完全な静的言語では、プロキシするタイプごとに1つのProxyクラスを作成するか、動的コード生成を使用する必要があります(確かに、多くの静的言語の標準ライブラリに含まれています。問題がこの原因を実行できないこと)。

動的言語のもう1つの使用例は、いわゆる「モンキーパッチ」です。多くの点で、これはパターンではなくアンチパターンですが、慎重に行うと便利な方法で使用できます。そして、モンキーパッチを静的言語で実装できなかった理論的な理由はありませんが、実際にそれを実装しているものは見たことがありません。


Goでこれをエミュレートできると思います。プロキシ化されたすべてのオブジェクトが持つ必要のあるメソッドのセットがあります(そうでない場合、アヒルはカクカクせず、すべてがバラバラになります)。これらのメソッドでGoインターフェースを作成できます。もっと考えなければなりませんが、私が考えていることはうまくいくと思います。
user7610

RealProxyとジェネリックを使用して、任意の.NET言語で同様のことができます。
LittleEwok

@LittleEwok-RealProxyはランタイムコード生成を使用します-私が言うように、多くの最新の静的言語にはこのような回避策がありますが、動的言語ではまだ簡単です。
ジュール

C#の拡張メソッドは、猿のパッチが安全になっているようなものです。既存のメソッドを変更することはできませんが、新しいメソッドを追加することはできます。
アンドリューは

3

はい、動的に型付けされた言語でのみ可能な多くのパターンとテクニックがあります。

モンキーパッチは、実行時にプロパティまたはメソッドがオブジェクトまたはクラスに追加される手法です。この手法は静的に型付けされた言語では不可能です。これは、コンパイル時に型と操作を検証できないことを意味するためです。別の言い方をすれば、言語がモンキーパッチをサポートしている場合、それは定義により動的言語です。

言語がモンキーパッチ(または実行時に型を変更する同様の手法)をサポートしている場合、静的に型チェックできないことが証明できます。したがって、これは現在の既存の言語の単なる制限ではなく、静的型付けの基本的な制限です。

したがって、引用は間違いなく正しいです。静的に型付けされた言語よりも動的な言語の方が多くのことが可能です。一方、特定の種類の分析は、静的に型付けされた言語でのみ可能です。たとえば、特定のタイプで許可される操作は常にわかっているため、コンパイルタイプで不正な操作を検出できます。実行時に操作を追加または削除できる場合、動的言語ではそのような検証は不可能です。

これが、静的言語と動的言語の競合に明らかな「最良」がない理由です。静的言語は、コンパイル時に異なる種類の能力と引き換えに実行時に特定の能力を放棄します。これにより、バグの数が減り、開発が容易になります。トレードオフには価値があると考える人もいれば、そうでない人もいます。

他の回答では、チューリングの等価性は、1つの言語で可能なことはすべての言語で可能であることを意味しています。しかし、これは続きません。静的言語でのモンキーパッチなどをサポートするには、基本的に静的言語内に動的サブ言語を実装する必要があります。もちろんこれは可能ですが、ホスト言語に存在する静的型チェックも失われるため、埋め込み動的言語でプログラミングしていると主張します。

バージョン4以降のC#は、動的に型指定されたオブジェクトをサポートしています。明らかに、言語設計者は、両方のタイプのタイピングを利用できるという利点を理解しています。しかし、それはまた、ケーキを持って食べられないことも示しています:C#で動的オブジェクトを使用すると、モンキーパッチのような機能が得られますが、これらのオブジェクトとの相互作用に対する静的型検証も失われます。


最後から2番目の段落に+1を付けることが、重要な議論だと思います。私はまだ、静的タイプと同じように、あなたがして何をモンキーパッチすることができますを完全に制御していても差があると主張するだろう
JKを。

2

引用の定式化を使用して、「型として機能しない」有用な設計パターンまたは戦略があるのだろうか?

はいといいえ。

プログラマーがコンパイラーよりも正確に変数の型を知っている状況があります。コンパイラーは何かがオブジェクトであることを知っているかもしれませんが、プログラマーは(プログラムの不変条件により)それが実際にはストリングであることを知っています。

この例をいくつか示します。

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

someMapの構築方法により、それsomeMap.get(T.class)がを返すことを知っていますFunction<T, String>。しかし、JavaはFunctionを持っていることだけを確信しています。

もう一つの例:

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

data.properties.rowCountは、スキーマに対してデータを検証したため、有効な参照および整数になることを知っています。そのフィールドが欠落している場合、例外がスローされます。しかし、コンパイラーは、それが例外をスローするか、何らかの汎用JSONValueを返すことのみを知っています。

もう一つの例:

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

「II6」は、データが3つの変数をエンコードする方法を定義します。フォーマットを指定したので、返されるタイプがわかります。コンパイラは、タプルを返すことのみを知っています。

これらすべての例の統一テーマは、プログラマーが型を知っていることですが、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関数はどのように機能しますか?リフレクションまたはマクロを使用しているようです。data ["properties.rowCount"]を静的言語でどのように入力できますか?結果の値が整数であることをどのようにして知ることができますか?
ウィンストンイーバート

2
@ NPSF3000、整数がわからない場合、どのように使用する予定ですか?JSON内のリスト内の要素が配列であることを知らずに、その要素をループする方法をどのように計画しますか?私の例のポイントは、それdata.propertiesがオブジェクトであることを知っていて、それdata.properties.rowCountが整数であることを知っていて、それらを使用するコードを簡単に書くことができるということでした。あなたの提案data["properties.rowCount"]は同じものを提供していません。
ウィンストンイーバート

1

以下に、C ++(静的型付け)では不可能なObjective-C(動的型付け)の例をいくつか示します。

  • いくつかの異なるクラスのオブジェクトを同じコンテナに入れます。
    もちろん、これには、後でコンテナの内容を解釈するために実行時の型検査が必要であり、静的型付けのほとんどの友人は、そもそもこれを行うべきではないことに反対します。しかし、宗教的な議論を超えて、これが役立つことがわかった。

  • サブクラス化せずにクラスを拡張します。
    Objective-Cでは、既存のクラスの新しいメンバー関数を定義できますNSString。たとえば、methodを追加して、stripPrefixIfPresent:言うことができます[@"foo/bar/baz" stripPrefixIfPresent:@"foo/"]NSSringリテラルの使用に注意してください@"")。

  • オブジェクト指向のコールバックの使用。
    JavaやC ++などの静的に型付けされた言語では、ライブラリがユーザー指定のオブジェクトの任意のメンバーを呼び出すことができるように、かなりの長さまで移動する必要があります。Javaでは、回避策はインターフェイス/アダプターペアと匿名クラスです。C++では、回避策は通常テンプレートベースであり、これはライブラリコードをユーザーコードに公開する必要があることを意味します。Objective-Cでは、オブジェクト参照とメソッドのセレクターをライブラリに渡すだけで、ライブラリはコールバックを簡単かつ直接呼び出すことができます。


void *にキャストすることにより、C ++で最初に行うことができますが、それは型システムを回避しているため、カウントされません。私は完全に型システム内で、拡張メソッドを使用してC#で2番目を実行できます。3番目については、「メソッドのセレクター」はラムダにできると思うので、正しく理解すれば、ラムダを使用して静的に型付けされた言語でも同じことができます。私はObjCに精通していません。
user7610

1
@JiriDanek「void *にキャストすることでC ++で最初にできる」とは言えませんが、要素を読み取るコードには実際の型を取得する方法がありません。型タグが必要です。それに、「これは<language>でできる」と言うのは、いつでもエミュレートできるので、これを見るのに適切で生産的な方法だとは思いません。重要なのは、表現力の向上と実装の複雑さです。また、言語に静的機能と動的機能(Java、C#)の両方がある場合、その言語は「静的」言語ファミリーにのみ属していると思われるようです。
コアダンプ

1
@JiriDanek void*だけでは動的な型付けではなく、型付けの不足です。しかし、はい、dynamic_cast、仮想テーブルなどにより、C ++は純粋に静的に型付けされません。それは悪いですか?
コアダンプ

1
必要に応じて型システムを破壊するオプションがあると便利です。必要なときにエスケープハッチを持っている。または誰かがそれを有用と考えました。そうでなければ、彼らはそれを言語に入れません。
user7610

2
@JiriDanek最後のコメントでかなり釘付けになったと思う。これらのエスケープハッチは、注意して使用すると非常に便利です。それにもかかわらず、大きな力には大きな責任があり、それを乱用する人はたくさんいます...したがって、他のすべてのクラスが定義から派生している汎用ベースクラスへのポインタを使用する方がずっと良いと感じていますObjective-CとJavaの両方で)、void*特定のオブジェクトタイプにキャストするよりも、RTTIに依存してケースを区別することをお勧めします。前者は台無しにすると実行時エラーが発生し、後者は未定義の動作になります。
cmaster
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.