オンラインでソフトウェアのランダムなトピックを読んでいるときに、ダックタイピングという用語に出くわしましたが、完全には理解していませんでした。
「ダックタイピング」とは何ですか?
オンラインでソフトウェアのランダムなトピックを読んでいるときに、ダックタイピングという用語に出くわしましたが、完全には理解していませんでした。
「ダックタイピング」とは何ですか?
ダックタイピング とは、演算がそのオペランドが満たさなければならない要件を正式に指定するのではなく、与えられたものでそれを試すことを意味します。
他の人が言ったこととは異なり、これは必ずしも動的言語や継承の問題に関連しているわけではありません。
タスクの例:Quack
オブジェクトのメソッドを呼び出します。
ダックタイピングを使用せずf
に、このタスクを実行する関数は、引数が何らかのメソッドをサポートする必要があることを事前に指定する必要がありますQuack
。一般的な方法は、インターフェイスの使用です
interface IQuack {
void Quack();
}
void f(IQuack x) {
x.Quack();
}
呼び出しf(42)
は失敗しますが、 -subtypeのインスタンスであるf(donald)
限り機能します。donald
IQuack
もう1つのアプローチは構造型ですが、この場合も、メソッドQuack()
が正式に指定されているため、事前に証明できないquack
場合はコンパイラーの障害が発生します。
def f(x : { def Quack() : Unit }) = x.Quack()
私たちも書くことができます
f :: Quackable a => a -> IO ()
f = quack
Haskellでは、Quackable
型クラスがメソッドの存在を保証します。
さて、私が言ったように、ダックタイピングシステムは要件を指定しませんが、何かがうまくいくかどうかを試すだけです。
したがって、Pythonの動的型システムは常にダックタイピングを使用します。
def f(x):
x.Quack()
サポートをf
取得した場合、すべてが正常です。そうでない場合は、実行時にクラッシュします。x
Quack()
しかし、ダックタイピングは動的型付けをまったく意味しません-実際、非常に人気がありますが完全に静的なダックタイピングアプローチがあり、要件もありません:
template <typename T>
void f(T x) { x.Quack(); }
x
この関数は、できるものが必要であることをまったく伝えないため、代わりにコンパイル時Quack
に試行し、すべてが機能する場合は問題ありません。
学術的な専門性/ニュアンスを避け、シンプルに保ちます(脚注^^^)。
「それがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルです。」-はい!しかし、それはどういう意味ですか??!
私たちは、「オブジェクト」が何であるかではなく、何ができるかに関心があります。例を挙げて開梱しましょう。
私が魔法の杖を持っていると想像してください。それは特別な力を持っています。杖を振って「ドライブ!」と言うと 車に、それでは、それは運転します!
それは他のものに作用しますか?わからない:だから私はトラックでそれを試してみます。うわー-それも運転します!次に、飛行機、電車、1つの森で試してみます(これらは、人々がゴルフボールを「運転」するために使用するゴルフクラブの一種です)。彼ら全員が運転します!
しかし、それは、例えば、ティーカップで機能しますか?エラー:KAAAA-BOOOOOOM!それはあまりうまくいきませんでした。====>茶碗は運転できません!! 当たり前!?
これは基本的にダックタイピングの概念です。これは、購入前に試す システムです。それがうまくいけば、すべてが順調です。しかし、手榴弾がまだ手にあるように失敗すると、顔が爆破します。
つまり、オブジェクトが何であるかではなく、オブジェクトが何を実行できるかに関心があります。
オブジェクトが実際に何であるかを懸念している場合、魔法のトリックは事前設定された許可されたタイプ(この場合は車)でのみ機能しますが、運転できる他のオブジェクト(トラック、モペット、トゥクトゥクなど)では失敗します。私たちの魔法の杖は車でのみ機能することを期待しているため、トラックでは機能しません。
言い換えると、このシナリオでは、魔法の杖は、オブジェクトが実行できること(たとえば、車、トラックなどが運転できるかどうか)ではなく、オブジェクトが何であるか(車ですか?)を非常に詳しく調べます。
トラックを運転させる唯一の方法は、魔法の杖にトラックと車の両方を期待させることができるかどうかです(おそらく「共通のインターフェースを実装する」ことによって)。それが何を意味するのかわからない場合は、今のところ無視してください。
ダックタイピングで重要なのは、オブジェクトが何であるかではなく、オブジェクトが実際に何ができるかです。
ここの完全なシーン:https ://youtu.be/hIdsjNGCGz4
CHUCKIE:わかりました、問題が発生しますか?
クラーク:問題ありません。ダックタイピングが実際に何であるかについての洞察を私に与えてくれることを願っていましたか?私の主張は、アヒルの結びつきは明確に定義されておらず、どちらも強くないということです
意志:[中断]…そしてどちらも強い型付けではありません。もちろん、それはあなたの主張です。あなたは1年生の大学院生です。おそらくStackOverflowで、ダックタイピングに関する記事を読み終えたところです。来月、ギャングオブフォーに到着するまで、そのことを確信するでしょう。 Google GoとOcamlが、構造的なサブタイピング構造を持つ統計的に型付けされた言語である方法について話します。それは来年まで続きます。おそらくここでマッツを逆流させ、プレルビー3.0のユートピアとGCでのサブタイピングのメモリ割り当て効果について話し合うまでです。
クラーク:[びっくり]マッツはその影響を大幅に過小評価しているので、実際にはそうしません— </ p>
ウィル:「マッツは、Ruby 3.0のGCがパフォーマンスに与える影響を劇的に過小評価しています。ドナルド・クヌース、The Art of Computer Programming、98ページから入手しましたか?ええ、私も読んでいます。私たちのためにすべてを盗用するつもりでしたか? —あなたはこの問題についてあなた自身の考えを持っていますか?またはそうします-あなたはあなたのこと、あなたはバーに入り、r / rubyのあいまいな一節を読んだ後、あなたはそれをあなた自身のものとしてポーンオフするふりをします—何人かの女の子を感動させるためだけにあなた自身の考え、私の友人を困らせる?
[クラークは唖然としている]
ウィル:あなたのような男の悲しいことを見てください。あなたは約50年後に自分で考え始め、人生には3つの確実性があるという事実を思い付くでしょう。一つは、そうしないでください。そして2つ目は、アヒルのように歩く場合はアヒルです。そして3つ目は、Ben KoshyによるSOの回答を介して、公共図書館で0セントで得られる教育に150グランドを投じました。
クラーク:ええ、でも私は学位を取得します。スキー旅行に行く途中のドライブスルーで、子供たちに(反応を介して)安価なhtmlを提供します。
ウィル:[笑顔]ええ、多分。しかし、少なくとも私は独創的ではありません。
(ビート)
ウィル:あなたはそれで問題を抱えていますか?外に出て、コードの問題が発生する可能性があると思います。
クラーク:問題ありません
今度いつか:
ウィル:あなたはリンゴが好きですか?
クラークは無益です。は?
ウィル:リンゴはどうですか?(ブーム:窓に手紙を叩きつけます。)私はグーグルから申し出をしなければなりません!(彼のインタビューの答えを示すクラークへの受諾の手紙を示しています:バブルソートアルゴリズムとダックタイピングに関する答え)
終わり
タイプのオブジェクトを取得し、Bird
そのwalk()
メソッドを呼び出す単純な関数を設計していると考えてください。あなたが考えることができる2つのアプローチがあります:
Bird
型のみを受け入れるか、コードがコンパイルされないことを確認する必要があります。誰かが私の関数を使いたいのなら、彼らは私がBird
sだけを受け入れることを知っている必要があります。objects
を取得し、オブジェクトのwalk()
メソッドを呼び出すだけです。だから、object
缶ならwalk()
それは正しいです。それができない場合、私の関数は失敗します。したがって、ここでは、オブジェクトがBird
何かであるかどうかは重要ではありません。それができることが重要ですwalk()
(これはダックタイピングです)。ダックタイピングが役立つ場合があることを考慮する必要があります。たとえば、Pythonはダックタイピングをよく使用します。
古いイディオムを繰り返す答えがたくさんあります。
アヒルのように見えて、アヒルのように鳴くなら、それはアヒルです
次に、ダックタイピングで何ができるかについての説明、または概念をさらに曖昧にしているように見える例に飛び込みます。
あまり助けにはなりません。
これは、私が見つけたダックタイピングについてのわかりやすい英語の答えでの最良の試みです。
ダックタイピングとは、オブジェクトが何であるかではなく、何ができるかによって定義されることを意味します。
これは、オブジェクトのクラス/タイプにはあまり関心がなく、オブジェクトに対して呼び出すことができるメソッドと、オブジェクトに対して実行できる操作に関心があることを意味します。私たちはそのタイプを気にせず、それが何ができるかを気にします。
ウィキペディアにはかなり詳細な説明があります:
http://en.wikipedia.org/wiki/Duck_typing
ダックタイピングは、オブジェクトの現在のメソッドとプロパティのセットが、特定のクラスまたは特定のインターフェイスの実装からの継承ではなく、有効なセマンティクスを決定する動的型付けのスタイルです。
重要な注意点は、ダックタイピングでは、開発者は実際の基礎となるタイプではなく、消費されるオブジェクトの部分に関心がある可能性が高いということです。
注:「として定義されている」:=
と読むことができます。
「ダックタイピング」とは、オブジェクトのタイプを最初にチェックして、そのメソッドがそのようなタイプの有効な呼び出しであるかどうかを確認するのではなく、入ってくるオブジェクトに対してメソッド(関数呼び出し)を試すことを意味します。
これを「メソッドを試す、タイプをチェックしない」タイピング、「メソッド呼び出しタイプチェック」、または単に「メソッド呼び出しタイピング」と呼びましょう。
以下のより長い説明では、これをより詳細に説明し、ばかげた、秘教的な、そして曖昧な用語「ダックタイピング」を理解するのに役立ちます。
より長い説明:
Pythonは上記の概念を実行します。次の関数例について考えてみます。
def func(a):
a.method1()
a.method2()
オブジェクト(入力パラメーターa
)が関数に入るfunc()
と、関数は(実行時に)このオブジェクトで指定されたメソッド(つまり、上記の例では)を呼び出そうとします。method1()
最初に、「有効な」method2()
かどうかを確認するのではありません。a
これらのメソッドを持つtype」。
したがって、これは実行時のアクションベースの試行であり、コンパイル時または実行時のタイプベースのチェックではありません。
ここで、このばかげた例を見てください。
def func(duck_or_duck_like_object):
duck_or_duck_like_object.quack()
duck_or_duck_like_object.walk()
duck_or_duck_like_object.fly()
したがって、ばかげたフレーズが生まれます:
それがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルです。
「ダックタイピング」を使用するプログラムは、オブジェクトのタイプを知らなくても、オブジェクトで呼び出されたメソッド(上記の例では、、、および)をquack()
単純walk()
に試行します。メソッドを試すだけです!すべての「ダックタイピング」言語が知っているか気にかけているのであれば、IT(関数に渡されるオブジェクト)はアヒルです!-すべての(アヒルのような)メソッドが機能するためです。fly()
(私自身の言葉を要約する):
「ダックタイピング」言語は、そのタイプをチェックしてはなりません(コンパイル時でも実行時でも)-それは気にしません。実行時にメソッドを試すだけです。彼らがうまくいけば、素晴らしい。そうでない場合は、実行時エラーがスローされます。
それがダックタイピングです。
私はこのばかげた「アヒル」の説明にとてもうんざりしています(この完全な説明がないとまったく意味がないためです!)、他の人もそう聞こえます。例:ここでのBKSpurgeonの回答から(太字で強調):
(「それがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルです。」)-はい!でもそれはどういう意味ですか??!」
今、私はそれを理解しました:最初にオブジェクトのタイプをチェックするのではなく、入ってくるオブジェクトに対してメソッドを試してみてください。
これを「オブジェクトがこれらのメソッドを持っていることを知る手段として最初にオブジェクトのタイプをチェックするのではなく、プログラムがオブジェクトがこれらのメソッドを持っているかどうかさえ知らずに呼び出されたメソッドを試行するランタイムチェック」と呼びます。もっと理にかなっています。しかし...それは言うには長すぎるので、人々は「ダックタイピング」のようなばかげているがキャッチーなことを言うのではなく、何年もの間お互いを混乱させたいと思うでしょう。
代わりに、これを「メソッドを試して、タイプをチェックしないでください」と呼びましょう。または、おそらく:「メソッド呼び出しタイプチェック」(略して「メソッド呼び出しタイピング」 )、または「メソッド呼び出しによる間接タイプチェック」。これは、特定のメソッドの呼び出しを「十分な証拠」として使用するためです。オブジェクトは、オブジェクトのタイプを直接チェックするのではなく、正しいタイプです。
この「メソッド呼び出しタイプチェック」(または、紛らわしいことに「ダックタイピング」と呼ばれる)は、動的型付けの一種であることに注意してください。ただし、すべての動的型付けが必ずしも「メソッド呼び出し型チェック」であるとは限りません。動的型付け、または実行時の型チェックは、オブジェクトの型を単に呼び出すのではなく、実際にオブジェクトの型をチェックすることによっても実行できるためです。タイプを知らなくても関数内のオブジェクト。
私は一般的な答えを出していないことを知っています。Rubyでは、変数やメソッドのタイプを宣言していません。すべてが単なるオブジェクトの一種です。したがって、ルールは「クラスはタイプではない」です
Rubyでは、クラスがタイプになることはありません(OK、ほとんどありません)。代わりに、オブジェクトのタイプは、そのオブジェクトが実行できることによってより明確に定義されます。Rubyでは、これをダックタイピングと呼びます。オブジェクトがアヒルのように歩き、アヒルのように話す場合、通訳者はそれをアヒルのように喜んで扱います。
たとえば、曲の情報を文字列に追加するルーチンを作成している場合があります。C#またはJavaのバックグラウンドを持っている場合は、次のように書きたくなるかもしれません。
def append_song(result, song)
# test we're given the right parameters
unless result.kind_of?(String)
fail TypeError.new("String expected") end
unless song.kind_of?(Song)
fail TypeError.new("Song expected")
end
result << song.title << " (" << song.artist << ")" end
result = ""
append_song(result, song) # => "I Got Rhythm (Gene Kelly)"
Rubyのダックタイピングを採用すれば、はるかに簡単なものを書くことができます。
def append_song(result, song)
result << song.title << " (" << song.artist << ")"
end
result = ""
append_song(result, song) # => "I Got Rhythm (Gene Kelly)"
引数の型を確認する必要はありません。<<(結果の場合)またはタイトルとアーティスト(曲の場合)をサポートしている場合は、すべてが正常に機能します。そうでない場合、メソッドはとにかく例外をスローします(タイプをチェックした場合と同じように)。しかし、チェックを行わないと、メソッドは突然はるかに柔軟になります。配列、文字列、ファイル、または<<を使用して追加するその他のオブジェクトを渡すことができ、それで問題なく動作します。
言語自体を見ると役立つ場合があります。それはしばしば私を助けます(私は英語を母国語とはしていません)。
でduck typing
:
1)この単語typing
は、キーボードで入力することを意味するのではなく(私の頭の中の永続的なイメージのように)、「そのことはどのような種類のものか」を判断することを意味します。
2)この言葉duck
は、その決定がどのように行われるかを表しています。それは、「アヒルのように歩くなら...それはアヒルだ」のように、一種の「ゆるい」決定です。アヒルかもしれないし、そうでないかもしれないので、それは「ゆるい」ですが、それが実際にアヒルであるかどうかは関係ありません。重要なのは、アヒルでできることと、アヒルが示す行動を期待できることです。私はそれにパン粉を与えることができます、そして物事は私に向かって行くか、私に突撃するか、または後退するかもしれません...しかしそれはグリズリーのように私をむさぼり食うことはありません。
ダックタイピング:
それがアヒルのように話したり歩いたりするなら、それはアヒルです
これは通常、アブダクションと呼ばれます(アブダクション推論またはレトロダクションとも呼ばれます。より明確な定義だと思います)。
C(結論、私たちが見るもの)とR(ルール、私たちが知っているもの)から、 P(前提、プロパティ)、言い換えれば与えられた事実を受け入れる/決定/仮定する
...医療診断のまさに基礎
アヒルの場合:C =歩く、話す、R =アヒルのように、P =アヒルです
プログラミングに戻る:
オブジェクトoにはメソッド/プロパティmp1があり、インターフェイス/タイプTにはmp1 が必要/定義されています
オブジェクトoにはメソッド/プロパティmp2が あり、インターフェイス/タイプTにはmp2が必要/定義されています
..。
したがって、 mp1 ...の定義を満たしている限り、任意のオブジェクトでmp1 ...を単に受け入れるだけでなく、コンパイラ/ランタイムも、 oがタイプTであるというアサーションで問題ないはずです。
そして、それは上記の例の場合ですか?ダックタイピングは本質的にタイピングではありませんか?それとも暗黙のタイピングと呼ぶべきですか?
ダックタイピングはタイプヒントではありません!
基本的に「ダックタイピング」を使用するには、特定のタイプではなく、共通のインターフェイスを使用して、より広い範囲のサブタイプ(継承については話しません。サブタイプを意味する場合は、同じプロファイル内に収まる「もの」を意味します)をターゲットにします。 。
情報を保存するシステムを想像することができます。情報を読み書きするには、ある種のストレージと情報が必要です。
ストレージの種類は、ファイル、データベース、セッションなどです。
インターフェイスは、ストレージタイプに関係なく、使用可能なオプション(メソッド)を通知します。つまり、この時点では何も実装されていません。言い換えれば、インターフェースは情報の保存方法について何も知りません。
すべてのストレージシステムは、まったく同じメソッドを実装することにより、インターフェイスの存在を知る必要があります。
interface StorageInterface
{
public function write(string $key, array $value): bool;
public function read(string $key): array;
}
class File implements StorageInterface
{
public function read(string $key): array {
//reading from a file
}
public function write(string $key, array $value): bool {
//writing in a file implementation
}
}
class Session implements StorageInterface
{
public function read(string $key): array {
//reading from a session
}
public function write(string $key, array $value): bool {
//writing in a session implementation
}
}
class Storage implements StorageInterface
{
private $_storage = null;
function __construct(StorageInterface $storage) {
$this->_storage = $storage;
}
public function read(string $key): array {
return $this->_storage->read($key);
}
public function write(string $key, array $value): bool {
return ($this->_storage->write($key, $value)) ? true : false;
}
}
だから今、あなたが情報を書いたり読んだりする必要があるたびに:
$file = new Storage(new File());
$file->write('filename', ['information'] );
echo $file->read('filename');
$session = new Storage(new Session());
$session->write('filename', ['information'] );
echo $session->read('filename');
この例では、最終的にStorageコンストラクターでDuckTypingを使用します。
function __construct(StorageInterface $storage) ...
お役に立てば幸いです;)
ダックタイピング技術によるツリートラバーサル
def traverse(t):
try:
t.label()
except AttributeError:
print(t, end=" ")
else:
# Now we know that t.node is defined
print('(', t.label(), end=" ")
for child in t:
traverse(child)
print(')', end=" ")
動的型付け、静的型付け、ダックタイピングを混同するのは混乱していると思います。ダックタイピングは独立した概念であり、Goのような静的な型付け言語でさえ、ダックタイピングを実装する型チェックシステムを持つことができます。型システムが(宣言された)オブジェクトのメソッドをチェックするが、型はチェックしない場合、それはダックタイピング言語と呼ばれる可能性があります。
ダックタイピングという用語は嘘です。
「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルです。」というイディオムがここで何度も繰り返されているのがわかります。
しかし、それはダックタイピング(または私たちが一般的にダックタイピングと呼んでいるもの)が何であるかではありません。私たちが議論しているダックタイピングのすべては、何かにコマンドを強制しようとしていることです。それが何であるかに関わらず、何かがいんちきするかどうかを見ること。しかし、オブジェクトがアヒルであるかどうかについての推論はありません。
真のダックタイピングについては、型クラスを参照してください。「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルです。」というイディオムに従います。型クラスでは、型が型クラスによって定義されたすべてのメソッドを実装する場合、それはメンバーと見なすことができます。その型クラスの(型クラスを継承する必要はありません)。したがって、特定のメソッド(quackおよびwalk-like-duck)を定義する型クラスDuckがある場合、それらの同じメソッドを実装するものはすべてDuckと見なすことができます(ダックを継承する必要があります)。
ダックタイピングでは、オブジェクトの適合性(たとえば、関数で使用される)は、そのオブジェクトのタイプではなく、特定のメソッドやプロパティが実装されているかどうかに基づいて決定されます。
たとえば、Pythonでは、このlen
関数はメソッドを実装する任意のオブジェクトで使用できます__len__
。そのオブジェクトが特定のタイプであるかどうかは関係ありません。たとえば、文字列、リスト、辞書、またはMyAwesomeClassは、これらのオブジェクトが__len__
メソッドを実装している限り、それらlen
で機能します。
class MyAwesomeClass:
def __init__(self, str):
self.str = str
def __len__(self):
return len(self.str)
class MyNotSoAwesomeClass:
def __init__(self, str):
self.str = str
a = MyAwesomeClass("hey")
print(len(a)) # Prints 3
b = MyNotSoAwesomeClass("hey")
print(len(b)) # Raises a type error, object of type "MyNotSoAwesomeClass" has no len()
言い換えれば、MyAwesomeClass
アヒルのように見え、アヒルのMyNotSoAwesomeClass
ようにクワックするのでアヒルですが、アヒルのようには見えず、クワクしないので、アヒルではありません!
私はこの有名な文を自分のやり方で理解しようとしています。
良いウェブサイトがあります。http://www.voidspace.org.uk/python/articles/duck_typing.shtml#id14
著者は、ダックタイピングを使用すると、独自の内部データ構造を持つ独自のクラスを作成できると指摘しましたが、通常のPython構文を使用してアクセスします。