Перейти к содержимому

Duck typing что это

  • автор:

Утиный ввод текста — Duck typing

Утиный ввод в компьютерном программировании — это применение теста утки — «Если он ходит, как утка, и крякает, как утка, то это должна быть утка» — чтобы определить, объект может использоваться для определенной цели. При обычном наборе текста пригодность определяется типом объекта. При утином вводе пригодность объекта определяется наличием определенных методов и свойств, а не типом самого объекта.

  • 1 Пример
  • 2 В языках со статической типизацией
  • 3 Сравнение с другими системами типов
    • 3.1 Системы структурных типов
    • 3.2 Протоколы и интерфейсы
    • 3.3 Шаблоны или общие типы

    Пример

    Это простой пример в Python 3, который демонстрирует, как любой объект может использоваться в любом контексте, до тех пор, пока он не используется способом, который он не поддерживает.

    class Duck: def fly (self): print («Утка в полете») class Sparrow: def fly (self): print («Sparrow flying») class Whale: def swim (self): print («Кит плавает») для животного в Duck (), Sparrow (), Whale (): animal.fly ()
    Утка, летящая Воробей, летящая AttributeError: объект 'Whale' не имеет атрибута 'fly'

    В языках со статической типизацией

    В некоторых языках со статической типизацией, таких как C # и Boo, проверка типа класса может быть указана для происходят во время выполнения, а не во время компиляции. Утиная типизация может быть достигнута в Java с использованием API MethodHandle.

    Сравнение с другими системами типов

    Системами структурных типов

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

    The TypeScript, OCaml, Scala, Go, Elm, Gosu и PureScript языки поддерживают структурную типизацию в разной степени.

    Протоколы и интерфейсы

    Протоколы и интерфейсы могут обеспечивать некоторые преимущества утиной печати, но утиная типизация отличается тем, что не имеет явного определения интерфейса. Например, если сторонняя библиотека реализует класс, который не может быть изменен, клиент не может использовать его экземпляр с интерфейсом, неизвестным этой библиотеке, даже если класс действительно удовлетворяет требованиям интерфейса. (Распространенным решением этой проблемы является Шаблон адаптера.) Утиная печать позволяет это. Опять же, для совместимости должен быть удовлетворен весь интерфейс.

    Шаблоны или универсальные типы

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

    Такие языки, как Python, Java и Objective-C, являются примерами утиной печати, поскольку в них можно создавать новые типы во время выполнения с помощью отражения и проверять, реализуют ли эти объекты определенные методы. С другой стороны, есть языки, которые полагаются на методы метапрограммирования во время компиляции (например, C ++ и его система шаблонов) и, таким образом, не попадают в категорию утиной печати; вместо этого в какой-то момент конвейера компиляции все типы заполнителей заменяются некоторыми конкретными типами, указанными в конкретном экземпляре. Несмотря на то, что в них возможно стирание определенного типа, проверка во время выполнения ограничена.

    См. Также

    • Методы расширения
    • UFCS
    • Слабая связь
    • Monkey patch
    • Язык динамического программирования

    Утиная типизация

    Латентная, неявная или утиная типизация (калька с англ. Duck typing ) — вид динамической типизации, применяемой в некоторых языках программирования (Perl, Smalltalk, Objective-C, Python, Ruby, Groovy, ColdFusion, Boo, Java, C#), когда границы использования объекта определяются его текущим набором методов и свойств, в противоположность наследованию от определённого класса. То есть считается, что объект реализует интерфейс, если он содержит все методы этого интерфейса, независимо от связей в иерархии наследования и принадлежности к какому-либо конкретному классу.

    Название термина пошло от английского «duck test» («тест на утку»), который в оригинале звучит как:

    «If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck». («Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка».).

    Утиная типизация решает такие проблемы иерархической типизации, как:

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

    Другим близким подходом является структурные подтипы в OCaml, где типы объектов совместимы, если совместимы сигнатуры их методов, независимо от объявленного наследования, причём всё это проверяется во время компиляции программы.

    См. также

    Ссылки

    • Утиная типизация в C#.
    • Утиная типизация в Python (англ.) .
    • Утиная типизация в Boo (англ.) .

    Что такое утиная типизация

    Во многих языках программирования есть такое правило: если нужно сразу обработать несколько объектов, то они должны быть одного типа — это позволит выполнять над ними одинаковые действия и получать предсказуемый результат.

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

    Это статья для общего развития — это знать интересно, но необязательно. Если нужны полезные практические знания, почитайте, как сделать нативное Android-приложение из веб-проекта на JavaScript.

    Что такое утиная типизация

    Утиная типизация — это приём в программировании, который можно описать фразой:

    Если что-то выглядит как утка, плавает как утка и крякает как утка, то, скорее всего, это утка.

    Сейчас на самом простом примере покажем, как это работает.

    В школьном Паскале переменная типа «строка» обозначается как String, а массивы — как Array, например так:

    var s : string; // строка
    var a : array[1..3] of integer; // массив из 3 чисел

    Пока всё в порядке: в строковой переменной мы будем хранить строку, а в массиве — какую-то последовательность чисел:

    Для вывода массива используем цикл for и в скобках указываем номера элементов:

    for i := 1 to 3 do write(a[i]); // выводим очередной элемент массива

    Но если нам нужно точно так же посимвольно вывести строку, то мы точно так же можем это сделать в цикле и указать номера символов в скобках:

    for i := 1 to Lenght(s) do write(s[i]); // выводим очередной символ строки

    Чтобы поменять значение элементов массива, можно обратиться к ним напрямую, указав в скобках номер элемента:

    Но если нам нужно обратиться к конкретному символу в строке, мы можем точно так же указать его индекс в квадратных скобках:

    Получается, что хотя строка и массив — это разные типы данных, но они ведут себя одинаково. А если строка выводится как массив, заполняется как массив и меняется как массив, то с практической точки зрения это и есть массив и с ним можно работать как с массивом. Это и есть утиная типизация, когда нам неважно, что там на самом деле — важно, как оно себя ведёт и как с ним работать.

    Сильнее всего утиная типизация проявляется в объектно-ориентированном программировании.

    Где применяется и как работает утиная типизация

    В ООП есть такое правило: если ты хочешь работать с объектами, ты должен использовать методы, которые прописаны в их классе. Перевод: «Ты можешь сделать с объектом только то, что разрешил автор, который прописал правила для всех подобных объектов».

    Допустим, у нас есть обработчик виртуальных мячиков. Его задача — считать, как виртуальные мячики отскакивают от разных поверхностей. Обработчик действует по такому алгоритму:

    1. К нему попадает несколько объектов класса Ball.
    2. У этого класса есть методы drop и catch — то есть мячик можно отпустить и поймать.
    3. Обработчик берёт первый объект, вызывает метод drop (отпускает), потом метод catch (ловит) и смотрит на результат вычислений.
    4. Если результат совпал с ожидаемым, переходит к следующему объекту, если нет — выводит сообщение.
    5. Так обработчик перебирает все объекты, которые к нему попали.

    Мы хотим, чтобы этот обработчик смог обработать наши объекты, но другого класса — Cube. То есть ронять и замерять не отскок мячика, а отскок кубика. Для этого наши объекты должны выглядеть снаружи для обработчика точно так же, как объекты класса Ball — это значит, они должны иметь методы drop и catch. С точки зрения обработчика, если объектом можно пользоваться как мячиком, это мячик.

    Этим часто пользуются разработчики, когда им нужно сделать совместимый интерфейс со старым алгоритмом, но работающий на новой логике. В этом случае они используют те же методы и названия, что у нужных объектов, но внутри у них новые алгоритмы. Благодаря этому микросервисы общаются между собой.

    Проще говоря, если что-то отвечает на запрос как почтовый сервер и присылает данные в формате почтового сервера, то с точки зрения микросервиса это почтовый сервер.

    Пример утиной типизации

    Представим, что в нашем коде есть фрагмент, который узнаёт длину попавшего к нему объекта и дальше что-то с ней делает. В Python за это отвечает функция len() , например:

    len([1,2,3,4,5]) // выдаст число 5
    len(‘Привет, это журнал «Код»!’) // выдаст число 25

    Но если в качестве параметра в len() попадёт объект нашего нового класса, то компьютер выдаст ошибку — он не знает, как считать длину объектов.

    len(my_new_super_class) // выдаст ошибку

    Чтобы обойти это ограничение, можно использовать утиную типизацию. Это значит, что если мы научим объект отдавать длину в ответ на запрос len(), то такой объект ничем не будет в этом плане отличаться от массива или строки. Добавим в наш класс метод __len__ — именно он неявно вызывается при любом подсчёте длины в строках или массивах:

    class my_new_super_class: def __len__(self): return 888

    Перевод: «Мы делаем новый класс объектов, который назвали Мой новый супер класс. Мы можем сделать на основании этого класса объект. Если спросить, какова будет длина этого объекта, то мы стабильно получим число 888»:

    len(my_new_super_class) // выдаст число 888

    Какие языки поддерживают утиную типизацию

    В том или ином виде утиная типизация есть во многих современных языках — с её помощью программисты обходят некоторые ограничения. Чаще всего её можно встретить в скриптовых языках, когда заранее часто неизвестно, с чем придётся работать, но понятно, каким способом это нужно сделать.

    Если говорить о самых популярных языках, где есть утиная типизация, то вот:

    • Python,
    • Perl,
    • Ruby,
    • JavaScript,
    • Go,
    • Scala,
    • Objective-C.

    А если покороче?

    Пожалуйста: для утиной типизации неважно, какому типу или классу принадлежат объекты. Главное, как они себя ведут и как работают, а что у них внутри на самом деле — это их дело.

    Получите ИТ-профессию

    В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.

    Структурная типизация — Основы Typescript

    В JavaScript возможно работать с объектами и классами одинаковым образом. При этом не нужно опираться ни на наследование, ни на интерфейсы. Нужны только ожидаемые поля и методы. Такой подход называют утиной типизацией (duck typing). Если что-то ходит как утка и крякает как утка — значит, это утка:

    const user =  firstName: 'Vasiliy', lastName: 'Kuzenkov', type: 'user' > const admin =  firstName: 'Kirill', lastName: 'Mokevnin', type: 'admin' > const formatUser = (user) => [user.type, ':', user.firstName, user.lastName].join(' '); formatUser(user); // 'user : Vasiliy Kuzenkov' formatUser(admin); // 'admin : Kirill Mokevnin' 

    В языках как Java нам бы потребовалось определить интерфейс, после отдельно имплементировать его для классов User и Admin . А в параметрах метода форматирования тип аргумента был бы этим интерфейсом.

    Другой вариант — написать метод с перегрузкой для этих двух случаев. Языки с таким поведением используют номинативную типизацию (nominative typing).

    Чтобы организовать подход утиной типизации в Java, нужно написать много дополнительного кода.

    Чтобы упростить переход с JavaScript на TypeScript и использовать проверки до выполнения кода, был выбран подход структурной типизации. С ней мы и познакомимся в этом уроке.

    С помощью структурной типизации мы можем легко переписать наш пример на TypeScript:

    const user =  firstName: 'Vasiliy', lastName: 'Kuzenkov', type: 'user' > const admin =  firstName: 'Kirill', lastName: 'Mokevnin', type: 'admin' > const formatUser = (user:  type: string, firstName: string, lastName: string >): string => [user.type, ':', user.firstName, user.lastName].join(' '); formatUser(user); // 'user : Vasiliy Kuzenkov' formatUser(admin); // 'admin : Kirill Mokevnin' 

    При этом структурная типизация не защищает нас от наличия дополнительных полей в объекте:

    const moderator =  firstName: 'Danil', lastName: 'Polovinkin', type: 'moderator', email: 'danil@polovinkin.com' > const formatUser = (user:  type: string, firstName: string, lastName: string >): string => [user.type, ':', user.firstName, user.lastName].join(' '); formatUser(moderator); // 'moderator : Danil Polovinkin' 

    В структурной типизации об объектном типе можно думать, как об описании структуры, которое накладывает ограничения на присваиваемые значения. Или как о множестве объектов, которые могут быть присвоены переменной с таким типом.

    object

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

    Аналогично операциям со множествами для объектных типов можно сформировать понимание пересечения и объединения в структурной типизации.

    При объединении | мы расширяем тип — увеличиваем число допустимых значений для типа. А при пересечении & — сужаем. Так мы уменьшаем число допустимых значений:

    type IntersectionUser =  username: string; password: string; > &  type: string; > const admin: IntersectionUser =  username: 'test', password: 'test', type: 'admin' > // Требуется совпадение c объектным типом и слева и справа от оператора & type UnionUser =  username: string; password: string; > |  type: string; > const user: UnionUser =  username: 'test', type: 'user' > // Достаточно совпадения с одним из объектных типов 

    object intersection

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

    Открыть доступ

    Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

    • 130 курсов, 2000+ часов теории
    • 1000 практических заданий в браузере
    • 360 000 студентов

    Наши выпускники работают в компаниях:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *