Каковы некоторые преимущества duck-typing против статического ввода?

Я исследую и экспериментирую больше с Groovy, и я пытаюсь обернуть свой ум вокруг плюсов и минусов реализации вещей в Groovy, которые я не могу/не делаю на Java. Динамическое программирование по-прежнему остается для меня просто концепцией, так как я был глубоко погружен в статические и сильно типизированные языки.

Groovy дает мне возможность утка-типа, но я действительно не вижу значения. Как Утиная печать более продуктивна, чем статическая? Что я могу сделать? в моей практике кода, чтобы помочь мне понять преимущества этого?

Я задаю этот вопрос с Groovy в виду, но я понимаю, что это не обязательно Groovy вопрос, поэтому я приветствую ответы из каждого лагеря кода.

12 ответов


далее, что лучше: EMACS или vi? Это одна из религиозных войн.

подумайте об этом так: любая программа, которая составляет правильно, будет правильным, если язык статически типизирован. Статическая типизация позволяет компилятору иметь достаточно информации для обнаружения несоответствий типов во время компиляции, а не во время выполнения. Это может быть раздражение, если вы делаете инкрементные виды программирования, хотя (я поддерживаю), если вы ясно думаете о своем с другой стороны, если вы создаете действительно большую программу, такую как операционная система или телефонный коммутатор, с десятками, сотнями или тысячами людей, работающих над ней, или с действительно высокими требованиями к надежности, тогда компилятор сможет обнаружить большой класс проблем для вас, не нуждаясь в тестовом случае, чтобы выполнить только правильный путь кода.

это не так, как если бы динамическая типизация-это новая и другая вещь: C, например, эффективно динамически типизированный, так как я всегда могу бросить foo* до bar*. Это просто означает, что тогда моя ответственность как программиста C никогда не использовать код, соответствующий bar* когда адрес действительно указывает на foo*. Но в результате проблем с большими программами, C выросли инструменты, такие как lint(1), укрепил свой тип системы с typedef и в конечном итоге разработал строго типизированный вариант на C++. (И, конечно же, C++, в свою очередь, разработал способы вокруг сильной типизации, со всеми разновидности отливок и дженериков / шаблонов и с RTTI.

еще одна вещь, хотя - - - не путайте "гибкое Программирование"с" динамическими языками". гибкое Программирование о том, как люди работают вместе в проекте: может ли проект адаптироваться к меняющимся требованиям для удовлетворения потребностей клиентов, сохраняя при этом гуманную среду для программистов? Это можно сделать с помощью динамически типизированных языков, и часто это так, потому что они могут быть более продуктивными (например, Ruby, Smalltalk), но это можно сделать, было сделано успешно, в C и даже ассемблере. На самом деле,Развитие Ралли даже использует гибкие методы (в частности, SCRUM) для маркетинга и документации.


многие комментарии для duck typing на самом деле не обосновывают претензии. Не "беспокоиться" о типе не является устойчивым для обслуживания или расширения приложения. У меня действительно была хорошая возможность увидеть Grails в действии над моим последним контрактом, и это довольно забавно смотреть на самом деле. Все счастливы о преимуществах в том, чтобы иметь возможность "создать приложение" и идти - к сожалению, все это догоняет вас на задней стороне.

Groovy кажется мне таким же. Конечно вы можете написать очень краткий код и, безусловно, есть хороший сахар в том, как мы работаем со свойствами, коллекциями и т. д... Но цена незнания того, что, черт возьми, передается взад и вперед, становится все хуже и хуже. В какой-то момент вы чешете голову, задаваясь вопросом, почему проект стал 80% тестирования и 20% работы. Урок здесь заключается в том, что "меньший" не делает код "более читаемым". Извините, ребята, его простая логика - чем больше вы должны знать интуитивно, тем больше усложняется процесс понимания этого кода. Вот почему GUI отступил, став слишком знаковым на протяжении многих лет-конечно, выглядит красиво, но WTH происходит не всегда очевидно.

У людей в этом проекте, казалось, были проблемы с "прибиванием" извлеченных уроков, но когда у вас есть методы, возвращающие либо один элемент типа T, массив T, ErrorResult или null ... это становится довольно очевидным.

одна вещь, работающая с Groovy, сделала для меня однако-удивительные оплачиваемые часы woot!


нет ничего плохого в статическом типировании, если вы используете Haskell, который имеет невероятную систему статического типа. Однако, если вы используете такие языки, как Java и C++, которые имеют ужасно калечащие системы типов, duck typing определенно является улучшением.

представьте, что вы пытаетесь использовать что-то настолько простое, как "карта" в Java (и нет, я не имею в виду структуры данных). Даже дженерики довольно слабо поддерживаются.


Duck typing калечит статическую проверку большинства современных IDE,которая может указывать на ошибки при вводе. Некоторые считают это преимуществом. Я хочу, чтобы IDE / компилятор сказал мне, что я сделал глупый трюк программиста как можно скорее.

мой самый последний любимый аргумент против duck typing происходит от проекта Grails DTO:

class SimpleResults {
    def results
    def total
    def categories
}

здесь results получается что-то вроде Map<String, List<ComplexType>>, который может быть обнаружен только по следам способ вызовы в разных классах, пока вы не найдете, где он был создан. Для смертельно любопытных,total - это сумма размеров List<ComplexType>s и categories - размер Map

возможно, это было ясно первоначальному разработчику, но плохой парень обслуживания (я) потерял много волос, отслеживая это.


немного сложно увидеть значение ввода утки, пока вы не использовали его некоторое время. Как только вы привыкнете к этому, вы поймете, насколько велика нагрузка на ваш ум, чтобы не иметь дело с интерфейсами или беспокоиться о том, какой именно тип что-то есть.


IMHO, преимущество duck typing увеличивается, когда вы придерживаетесь некоторых соглашений, таких как последовательное именование переменных и методов. Возьмем пример из Кен Г, Я думаю, что это будет лучше всего читать:

class SimpleResults {
    def mapOfListResults
    def total
    def categories
}

предположим, вы определяете контракт на некоторую операцию под названием " вычисление(A, B)", где A и B придерживаются другого контракта. В псевдокоде будет написано:--8-->

Long calculateRating(A someObj, B, otherObj) {

   //some fake algorithm here:
   if(someObj.doStuff('foo') > otherObj.doStuff('bar')) return someObj.calcRating());
   else return otherObj.calcRating();

}

если вы хотите реализовать это на Java, как A, так и B должны реализовать какой-то интерфейс, который читает что-то вроде этого:

public interface MyService {
    public int doStuff(String input);
}

кроме того, если вы хотите обобщить контракт на расчет рейтингов (допустим, у вас есть другой алгоритм расчета рейтинга), вам также нужно создать интерфейс:

public long calculateRating(MyService A, MyServiceB);

с уткой набрав, вы можете бросить свой интерфейсы и просто полагайтесь на то, что во время выполнения оба A и B будут правильно реагировать на ваш doStuff() звонки. Нет необходимости в конкретном контракте определение. Это может сработать для вас, но может и против вас.

недостатком является то, что вы должны быть очень осторожны, чтобы гарантировать, что ваш код не сломается, когда некоторые другие лица изменяют его (т. е. другой человек должен знать о неявном договоре на имя метода и аргументы).

обратите внимание, что это особенно усугубляется в Java, где синтаксис не так лаконичен, как мог бы быть (по сравнению с Scala например). Контр-пример это основы лифт, где они говорят, что количество SLOC фреймворка похоже на рейлинги, но тестовый код имеет меньше строк, потому что им не нужно реализовывать проверки типов в тестах.


вот один сценарий, где утка набрав экономит работу.

вот очень тривиальный класс

class BookFinder {
    def searchEngine

    def findBookByTitle(String title) {
         return searchEngine.find( [ "Title" : title ] ) 
    }
}

теперь для модульного теста:

void bookFinderTest() {
    // with Expando we can 'fake' any object at runtime.
    // alternatively you could write a MockSearchEngine class.
    def mockSearchEngine = new Expando()
    mockSearchEngine.find = {
        return new Book("Heart of Darkness","Joseph Conrad")
    }

    def bf = new BookFinder()
    bf.searchEngine = mockSearchEngine
    def book = bf.findBookByTitle("Heart of Darkness")
    assert(book.author == "Joseph Conrad"
}

мы смогли заменить Expando для SearchEngine, из-за отсутствия статической проверки типа. При статической проверке типов нам пришлось бы убедиться, что SearchEngine является интерфейсом или, по крайней мере, абстрактным классом, и создать его полную макет. Это трудоемко, или вы можете использовать изощренная одноцелевая издевательская структура. Но утиный набор текста является универсальным и помог нам.

из-за duck typing наш модульный тест может предоставить любой старый объект вместо зависимости, если он реализует вызываемые методы.

чтобы подчеркнуть-вы можете сделать это на статически типизированном языке, с тщательным использованием интерфейсов и иерархий классов. Но с duck typing вы можете сделать это с меньшим мышлением и меньше нажатий на клавиши.

это преимущество duck typing. Это не означает, что динамическая типизация является правильной парадигмой для использования во всех ситуациях. В моих проектах Groovy мне нравится переключаться обратно на Java в обстоятельствах, когда я чувствую, что предупреждения компилятора о типах помогут мне.


С TDD + 100% покрытие кода + IDE инструменты для постоянного запуска моих тестов, я не чувствую необходимости статического ввода больше. Без сильных типов мое модульное тестирование стало таким простым (просто используйте карты для создания макетов объектов). Особенно, когда вы используете дженерики, вы можете увидеть разницу:

//Static typing 
Map<String,List<Class1<Class2>>> someMap = [:] as HashMap<String,List<Class1<Class2>>>

vs

//Dynamic typing
def someMap = [:]   

дело не в том, что утиная печать более продуктивна, чем статическая, поскольку она просто отличается. При статическом вводе вам всегда нужно беспокоиться о том, что ваши данные являются правильным типом, и в Java он отображается через приведение к правильному типу. С duck typing тип не имеет значения, пока он имеет правильный метод, поэтому он действительно просто устраняет много хлопот литья и преобразования между типами.


@Chris Bunch

дело не в том, что статическая типизация более продуктивна, чем Утиная, поскольку она просто отличается. С duck typing вам всегда нужно беспокоиться о том, что ваши данные имеют правильный метод, а в Javascript или Ruby он отображается через множество тестов методов. При статическом типировании это не имеет значения, если это правильный интерфейс, поэтому он действительно просто устраняет много хлопот тестирования и преобразования между типами.

Извините, но я должен был сделать он...


мое мнение:

динамически типизированные или утиные типизированные языки-это игрушки. Вы не можете получить Intellisense, и вы теряете время компиляции (или время редактирования - при использовании реальной IDE, такой как VS, а не мусор, который другие люди считают IDEs).

держитесь подальше от каждого языка, который не является статически типизированным, все остальное-просто мазохизм.


для меня они не сильно отличаются, если вы видите динамически типизированные языки как просто форму статического ввода, где все наследуется от достаточно абстрактного базового класса.

проблемы возникают, когда, как многие указывали, вы начинаете получать странно с этим. Кто-то указал на функцию, которая возвращает один объект, коллекцию или null. Пусть функция возвращает определенный тип, а не несколько. Используйте несколько функций для одного vs коллекция.

все сводится к тому, что любой может написать плохой код. Статическая типизация-это отличное устройство безопасности, но иногда шлем мешает, когда вы хотите почувствовать ветер в волосах.