Является ли принцип подстановки Лискова несовместимым с интроспекцией или уткой?

11

Правильно ли я понимаю, что принцип подстановки Лискова не может соблюдаться в языках, где объекты могут сами себя проверять, как, например, в языках с утиной типизацией?

Например, в Ruby, если класс Bнаследует от класса A, то для каждого объекта xиз A, x.classсобирается вернуться A, но если xэто объект B, x.classне собирается возвращаться A.

Вот заявление LSP:

Пусть д (х) является свойством доказуемо об объектах х типа Т . Тогда д (у) должно быть доказуемо для объектов , у типа S , где S является подтипом T .

Так в Ruby, например,

class T; end
class S < T; end

нарушать LSP в этой форме, о чем свидетельствует свойство q (x) =x.class.name == 'T'


Дополнение. Если ответ «да» (LSP несовместим с интроспекцией), тогда мой другой вопрос: есть ли какая-то измененная «слабая» форма LSP, которая может быть применима для динамического языка, возможно, при некоторых дополнительных условиях и только с особыми типами из свойств .


Обновить. Для справки, вот еще одна формулировка LSP, которую я нашел в Интернете:

Функции, которые используют указатели или ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.

И другой:

Если S является объявленным подтипом T, объекты типа S должны вести себя так же, как и объекты типа T, если они обрабатываются как объекты типа T.

Последний отмечен:

Обратите внимание, что LSP - это все об ожидаемом поведении объектов. Следовать LSP можно, только если ясно, каково ожидаемое поведение объектов.

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

Тогда является ли LSP свойством не пары классов в языке программирования, а пары классов вместе с заданным набором свойств, удовлетворяемых классом предка? Практически, означает ли это, что для создания подкласса (класса-потомка), уважающего LSP, должны быть известны все возможные варианты использования класса-предка? Согласно LSP, класс предков должен быть заменен любым классом-потомком, верно?


Обновить. Я уже принял ответ, но я хотел бы добавить еще один конкретный пример из Ruby, чтобы проиллюстрировать вопрос. В Ruby каждый класс является модулем в том смысле, что Classкласс является потомком Moduleкласса. Однако:

class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module

module M; end
M.class # => Module

o = Object.new

o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)
Алексей
источник
2
Почти все современные языки обеспечивают некоторую степень самоанализа, поэтому этот вопрос не является специфическим для Ruby.
Йоахим Зауэр
Я понимаю, я привел Руби только в качестве примера. Я не знаю, может быть, в некоторых других языках с самоанализом существуют некоторые «слабые формы» LSP, но, если я правильно понял принцип, он несовместим с самоанализом.
Алексей
Я удалил "Рубин" из заголовка.
Алексей
2
Краткий ответ: они совместимы. Вот сообщение в блоге, с которым я в основном согласен: Принцип подстановки Лискова при
наборе
2
@Alexey Свойства в этом контексте являются инвариантами объекта. Например, неизменяемые объекты имеют свойство, что их значения не изменяются. Если вы посмотрите на хорошие модульные тесты, они должны проверять именно эти свойства.
Стефф

Ответы:

29

Вот фактический принцип :

Позвольте q(x)быть свойство доказуемо для объектов xтипа T. Тогда q(y)должно быть доказуемо для объектов yтипа, Sгде Sесть подтип T.

И отличное резюме Википедии :

В нем говорится, что в компьютерной программе, если S является подтипом T, объекты типа T могут быть заменены объектами типа S (то есть объекты типа S могут быть заменены объектами типа T) без изменения какого-либо из желательные свойства этой программы (правильность, выполненная задача и т. д.).

И некоторые соответствующие цитаты из бумаги:

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

Спецификация типа включает следующую информацию:
- имя типа;
- описание пространства значений типа;
- Для каждого из методов типа:
--- его имя;
--- его подпись (включая сигнальные исключения);
--- Его поведение с точки зрения предусловий и постусловий.

Итак, к вопросу:

Правильно ли я понимаю, что принцип подстановки Лискова не может соблюдаться в языках, где объекты могут сами себя проверять, как, например, в языках с утиной типизацией?

Нет.

A.classвозвращает класс.
B.classвозвращает класс.

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

Но давайте рассмотрим статически, структурный (утиный) типизированный язык. В этом случае A.classбудет возвращен тип с ограничением, которым он должен быть, Aили подтипом A. Это обеспечивает статическую гарантию того, что любой подтип Aдолжен предоставлять метод T.class, результатом которого является тип, удовлетворяющий этому ограничению.

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

Telastyn
источник
1
«Поскольку вы можете сделать один и тот же вызов для более конкретного типа и получить совместимый результат, LSP удерживает». LSP держится, если результаты идентичны, если я правильно понял. Может быть, существует некоторая «недельная» форма LSP по отношению к заданным ограничениям, требующая, чтобы вместо всех свойств выполнялись только заданные ограничения. В любом случае, я был бы признателен за любую ссылку.
Алексей
@ Алексей отредактирован, чтобы включить то, что означает LSP. Если я могу использовать B там, где я ожидаю A, тогда LSP держится. Мне любопытно, как вы думаете, класс Ruby возможно нарушает это.
Теластин
3
@Alexey - Если ваша программа содержит fail unless x.foo == 42и подтип возвращает 0, это то же самое. Это не сбой LSP, это нормальная работа вашей программы. Полиморфизм не является нарушением LSP.
Теластин
1
@ Алексей - Конечно. Давайте предположим, что это свойство. В этом случае ваш код нарушает LSP, поскольку он не позволяет подтипам иметь одинаковое семантическое поведение. Но это не особенно особенное для динамических или типизированных языков. Они не делают ничего в своем языковом дизайне, который вызывает нарушение. Код, который вы написали, делает. Помните, что LSP - это принцип разработки программы (следовательно, это следует из определения), а не математическое свойство программ.
Теластин
6
@ Алексей: если вы пишете что-то, что зависит от x.class == A, то ваш код нарушает LSP , а не язык. Можно написать код, который нарушает LSP практически на любом языке программирования.
Андрес Ф.
7

В контексте LSP «свойство» - это то, что можно наблюдать для типа (или объекта). В частности, речь идет о «доказуемой собственности».

Такое «свойство» может существовать foo()методом, который не имеет возвращаемого значения (и следует контракту, установленному в его документации).

Убедитесь, что вы не путаете этот термин с «свойством», так как « classсвойство каждого объекта в Ruby». Такое «свойство» может быть «свойством LSP», но это не то же самое автоматически!

Теперь ответ на ваши вопросы во многом зависит от того, насколько строгое вы определяете «свойство». Если вы говорите , «свойство класса Aявляется то , что .classвозвращает тип объекта», то на Bсамом деле делает имеет это свойство.

Если, однако, вы определяете «свойство» быть « .classвозвращается A», то очевидно , что Bэто не имеет это свойство.

Однако второе определение не очень полезно, поскольку вы, по сути, нашли обходной способ объявления константы.

Йоахим Зауэр
источник
Я могу думать только об одном определении «свойства» программы: для заданного ввода оно возвращает заданное значение или, в более общем случае, при использовании в качестве блока в другой программе, что другая программа для данного ввода вернет данные значения. С этим определением я не вижу, что значит « .classвернет тип объекта». Если это означает, что x.class == x.classэто не интересная собственность.
Алексей
1
@Alexey: Я обновил свой вопрос, пояснив, что означает «свойство» в контексте LSP.
Йоахим Зауэр
2
@Alexey: изучая статью, я не нахожу конкретного определения или «свойства». Это, вероятно, потому, что термин используется в общем смысле CS "что-то, что можно наблюдать / доказать о каком-то объекте". Это не имеет ничего общего с тем, что другое означает «поле объекта».
Йоахим Зауэр
4
@Alexey: Я не знаю, что еще могу вам сказать. Я использую определение «свойство - это некое качество или атрибут объекта». «Цвет» - это свойство физического, видимого объекта. «Плотность» - это свойство материала. «Наличие указанного метода» является свойством класса / объекта.
Йоахим Зауэр
4
@Alexey: Я думаю, что вы бросаете ребенка с водой из ванны: просто потому, что LSP нельзя удерживать в некоторых свойствах, не означает, что он бесполезен или «не поддерживается ни на одном языке». Но это обсуждение зашло бы далеко здесь.
Йоахим Зауэр
5

Насколько я понимаю, в самоанализе нет ничего, что было бы несовместимо с LSP. По сути, пока объект поддерживает те же методы, что и другой, оба должны быть взаимозаменяемыми. То есть, если ваш код ожидает Addressобъект, то это не имеет значения , если это CustomerAddressили WarehouseAddress, до тех пор, как обеспечить (например) getStreetAddress(), getCityName(), getRegion()и getPostalCode(). Вы, конечно, можете создать какой-то тип декоратора, который принимает объект другого типа и использует интроспекцию для предоставления требуемых методов (например, DestinationAddressкласс, который принимает Shipmentобъект и представляет адрес доставки как Address), но это не обязательно и, безусловно, не не препятствует применению LSP.

TMN
источник
2
@Alexey: объекты "одинаковы", если они поддерживают одни и те же методы. Это означает то же имя, то же число и тип аргументов, тот же тип возврата и те же побочные эффекты (при условии, что они видимы для вызывающего кода). Методы могут вести себя совершенно по-разному, но пока они соблюдают контракт, это нормально.
TMN
1
@ Алексей: а зачем мне такой контракт? Какое фактическое использование выполняет этот контракт? Если бы я был такой контракт , я мог бы просто заменить каждое вхождение в x.class.nameс 'A' , эффективно делая x.class.name бесполезным .
Йоахим Зауэр
1
@Alexey: еще раз: только то, что вы можете определить контракт, который не может быть выполнен путем расширения другого класса, не нарушает LSP. Это просто означает, что вы создали нерасширяемый класс. Если я определяю метод для «возврата, если предоставленный блок кода заканчивается за конечное время », то у меня также есть контракт, который не может быть выполнен. Это не значит, что программирование бесполезно.
Йоахим Зауэр
2
@Alexey пытается определить, x.class.name == 'A'является ли анти-шаблон в типизации утки: в конце концов, типизация утки происходит от "Если она крякает и ходит как утка, это утка". Так что, если он ведет себя как Aи AA
соблюдает
1
@Alexey Вам были представлены четкие определения. Класс объекта не является частью его поведения или контракта, или как вы хотите его называть. Вы называете «свойство» «полем объекта, таким как x.myField», которое, как было указано, НЕ является тем же. В этом контексте свойство больше похоже на математическое свойство, например, инварианты типа. Кроме того, это анти-шаблон, чтобы проверить точный тип, если вы хотите печатать утку. Итак, что у вас за проблема с LSP и опечаткой, опять же? ;)
Андрес Ф.
4

Просматривая оригинальную статью Барбары Лисков, я нашел, как завершить определение Википедии, чтобы LSP действительно мог быть удовлетворен практически на любом языке.

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

Вот первая важная цитата из статьи:

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

И вот второй, объясняющий, что такое спецификация типа :

Спецификация типа включает следующую информацию:

  • Название типа;
  • Описание пространства значений типа;
  • Для каждого из методов типа:
    • Его имя;
    • Его подпись (включая сигнальные исключения);
    • Его поведение с точки зрения предусловий и постусловий.

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

Я считаю, что ответ Теластина наиболее близок к тому, что я искал, потому что «ограничения» были упомянуты явно.

Алексей
источник
Теластин, если бы ты мог добавить эти цитаты в свой ответ, я бы предпочел принять твои, чем мои собственные.
Алексей
2
разметка не совсем то же самое, и я немного изменил эмпезу, но цитаты были включены.
Теластин
Извините, Йоахим Зауэр уже упомянул «доказуемые» свойства, которые вам не понравились. В общем, вы просто переформулировали существующие ответы. Честно говоря, я не знаю, что вы ищете ...
Андрес Ф.
Нет, это не объяснили «доказуемо от чего?». Свойство x.class.name = 'A'доказуемо для всего xкласса, Aесли вы позволите слишком много знаний. Спецификация типа не была определена, и ее точное отношение к LSP также не было, хотя неофициально были даны некоторые указания. Я уже нашел то, что искал в статье Лискова и ответил на мой вопрос выше.
Алексей
Я думаю, что слова, которые вы подбодрили, являются ключевыми. Если супертип документирует, что для любого xиз этого типа, x.woozleбудет давать undefined, то ни один тип, для которого x.woozleне дает, undefinedбудет правильным подтипом. Если супертип ничего не документирует x.woozle, тот факт, что использование x.woozleсупертипа приведет к выходу undefined, ничего не будет означать, что может повлиять на подтип.
суперкат
3

Цитируя статью Википедии о LSP , «заменяемость является принципом объектно-ориентированного программирования». Это принцип и часть дизайна вашей программы. Если вы пишете код, который зависит от x.class == A, то это ваш код нарушает LSP. Обратите внимание, что этот тип испорченного кода также возможен в Java, нет необходимости вводить утку.

Ничто в наборе утки по своей сути не нарушает LSP. Только если вы его неправильно используете, как в вашем примере.

Дополнительная мысль: неужели проверка в явном виде на предметный класс не побеждает цель утиной типизации?

Андрес Ф.
источник
Андрес, не могли бы вы дать определение LSP, пожалуйста?
Алексей
1
@Alexey Точное определение LSP указано в Википедии в терминах подтипов. Неформальное определение есть Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).. Точный класс объекта НЕ является одним из «желательных свойств программы»; в противном случае это противоречило бы не только типизации уток, но и подтипам в целом, включая Java-вкус.
Андрес Ф.
2
@Alexey Также обратите внимание, что ваш пример x.class == Aнарушает как LSP, так и утку. Нет смысла использовать утку, если вы собираетесь проверять фактические типы.
Андрес Ф.
Андрес, это определение не достаточно точное для меня, чтобы понять. Какая программа, данная или любая? Что такое желаемое свойство? Если класс находится в библиотеке, разные приложения могут посчитать желаемыми разные свойства. А не вижу, как строка кода может нарушать LSP, потому что я думал, что LSP является свойством пары классов в данном языке программирования: либо ( A, B) удовлетворяют LSP, либо нет. Если LSP зависит от кода, используемого в другом месте, то не объясняется, какой код разрешен. Я надеюсь найти что-то здесь: cse.ohio-state.edu/~neelam/courses/788/lwb.pdf
Алексей
2
@Alexey LSP держит (или нет) для определенного дизайна. Это то, что нужно искать в дизайне; это вообще не свойство языка. Это не становится более точным, чем фактическое определение Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T. Очевидно, что x.classэто не одно из интересных свойств здесь; иначе полиморфизм Java не сработает. В вашей "проблеме x.class" нет ничего, что присуще утке. Вы согласны до сих пор?
Андрес Ф.