我在网上阅读随机主题时遇到了鸭子打字这个术语,并没有完全理解它。
什么是“鸭子打字”?
这是dynamic languages中使用的术语,没有strong typing。
我们的想法是,您不需要类型来调用对象上的现有方法 - 如果在其上定义了方法,则可以调用它。
这个名字来自短语“如果它看起来像鸭子,像鸭子一样嘎嘎叫,那就是鸭子”。
Wikipedia有更多的信息。
鸭子打字不是打字!
基本上为了使用“鸭子打字”你不会针对特定的类型,而是更广泛的子类型(不是谈论继承,当我指的是属于同一个配置文件中的“事物”的子类型时)通过使用公共接口。
您可以想象一个存储信息的系统。为了写/读信息,您需要某种存储和信息。
存储类型可以是:文件,数据库,会话等。
无论存储类型如何,界面都会让您知道可用的选项(方法),这意味着此时没有任何实现!换句话说,接口对如何存储信息一无所知。
每个存储系统都必须通过实现它的方法来了解接口的存在。
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构造函数中使用Duck Typing:
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
。
不使用duck-typing,执行此任务的函数f
必须事先指定其参数必须支持某些方法Quack
。常见的方法是使用接口
interface IQuack {
void Quack();
}
void f(IQuack x) {
x.Quack();
}
调用f(42)
失败,但f(donald)
工作只要donald
是IQuack
子类型的实例。
另一种方法是structural typing - 但同样,方法Quack()
正式指定任何无法事先证明它的quack
s会导致编译器失败。
def f(x : { def Quack() : Unit }) = x.Quack()
我们甚至可以写
f :: Quackable a => a -> IO ()
f = quack
在Haskell中,Quackable
类型类确保了我们方法的存在。
好吧,正如我所说,鸭子打字系统没有指定要求,只是尝试任何有效的方法。
因此,Python的动态类型系统总是使用duck typing:
def f(x):
x.Quack()
如果f
获得支持x
的Quack()
,一切都很好,如果没有,它会在运行时崩溃。
但鸭子打字并不意味着动态打字 - 事实上,有一种非常流行但完全静态的鸭子打字方法也没有给出任何要求:
template <typename T>
void f(T x) { x.Quack(); }
该函数没有以任何方式告诉它想要一些可以x
的Quack
,所以相反它只是在编译时尝试,如果一切正常,那就没关系。
讨论这个问题的语义是相当细微的(并且非常学术性),但这里是一般性的想法:
鸭子打字
(“如果它像鸭子一样走路,像鸭子那样嘎嘎叫,那就是鸭子。”) - 是的!但是,这是什么意思??!最好通过示例说明:
示例:动态键入的语言
想象一下,我有一根魔杖。它有特殊的权力。如果我挥动魔杖说“开车!”到了一辆车,那么,它开车!
它适用于其他事情吗?不确定:所以我在卡车上试试。哇 - 它也开车了!然后我在飞机,火车和1个伍兹(它们是一种人们用来“驾驶”高尔夫球的高尔夫球杆)上尝试。他们全都开车!
但它会起作用吗,一个茶杯?错误:KAAAA-BOOOOOOM!没那么好的。 ====>茶杯不能开车!!咄!?
这基本上是鸭子打字的概念。这是一个先试后买的系统。如果它有效,一切都很好。但如果它失败了,那么它会在你的脸上爆炸。
换句话说,我们对对象可以做什么感兴趣,而不是对象是什么。
示例:静态类型语言
如果我们关心对象实际上是什么,那么我们的魔术技巧将只适用于预先设定的授权类型 - 在这种情况下是汽车,但在其他可以驱动的物体上会失败:卡车,轻便摩托车,嘟嘟车等。它不适用于卡车,因为我们的魔杖预计它只适用于汽车。
换句话说,在这种情况下,魔杖非常仔细地看待物体是什么(它是汽车?)而不是物体可以做什么(例如汽车,卡车等是否可以驾驶)。
你能驾驶卡车开车的唯一方法就是如果你能以某种方式获得魔术棒以期望卡车和汽车(也许是通过“实现一个共同的界面”)。如果您不知道这意味着什么,那么暂时忽略它。
摘要:关键外卖
鸭子打字的重要之处在于对象实际上可以做什么,而不是对象是什么。
考虑一下你正在设计一个简单的函数,它获取Bird
类型的对象并调用它的walk()
方法。您可以想到两种方法:
Bird
,否则它们的代码将无法编译。如果有人想使用我的功能,他必须意识到我只接受Bird
sobjects
,我只是调用对象的walk()
方法。所以,如果object
可以walk()
它是正确的,如果它不能我的功能将失败。所以这里不重要的对象是Bird
或其他任何东西,重要的是它可以walk()
(这是鸭打字)必须考虑鸭子打字在某些情况下可能有用,例如Python使用鸭子打字很多。
维基百科有一个相当详细的解释:
http://en.wikipedia.org/wiki/Duck_typing
duck typing是一种动态类型,其中对象的当前方法和属性集确定有效语义,而不是从特定类或特定接口的实现继承。
重要的注意事项可能是,对于鸭子类型,开发人员更关心的是消耗的对象部分而不是实际的底层类型。
我看到许多重复旧习语的答案:
如果它看起来像鸭子,像鸭子一样嘎嘎叫,它就是一只鸭子
然后深入解释你可以用鸭子打字做什么,或者一个似乎进一步混淆概念的例子。
我没有找到那么多帮助。
这是我发现的关于鸭子打字的简单英语答案的最佳尝试:
Duck Typing意味着一个对象是由它可以做什么而不是它是什么来定义的。
这意味着我们不太关心对象的类/类型,而更关心可以在其上调用哪些方法以及可以对其执行哪些操作。我们不关心它的类型,我们关心它能做什么。
鸭子打字:
如果它像鸭子一样说话和走路,那么它就是一只鸭子
这通常被称为绑架(诱导推理或称为重新引入,我认为更清晰的定义):
回到编程:
所以,不仅仅是在任何对象上接受mp1 ...只要它满足mp1的某些定义...,编译器/运行时也应该可以使用类型为T的断言o
好吧,上面的例子是这样吗? Duck打字基本上没有打字吗?或者我们应该称之为隐式打字?
我知道我没有给出一般性答案。在Ruby中,我们不声明变量或方法的类型 - 一切都只是某种对象。所以规则是“类不是类型”
在Ruby中,类永远不会(好的,几乎从不)类型。相反,对象的类型更多地由该对象可以做什么来定义。在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
这个词表达了如何做出决定;这是一种“松散”的决定,如:“如果它像鸭子一样走路......那么它就是一只鸭子”。它是“松散的”,因为它可能是鸭子或者可能不是,但它是否真的是鸭子并不重要;重要的是我可以用它来做我能用鸭子做的事情,并期待鸭子表现出的行为。我可以给面包屑喂它,这东西可能会朝我走来,或者向我充电或退后......但它不会像灰熊那样吞噬我。