Pull to refresh
-2
0
Send message
Метод add по умолчанию уже возвращает и сумму, и разность,: add(2,2) => 4, add(2,-2) => 0. Работа reduce(add, collection) не изменится, если add будет возвращать разницу. Поэтому вызывающий код никак не может предполагать, что add вернёт именно сумму. И вообще вызывающий метод в принципе не должен делать никаких предположений относительно смысла возвращаемого значения, а только относительно множества допустимых возвращаемых значений. Все проверки «от дурака» находятся внутри вызывающего метода, так как всегда ожидается, что вызываемый метод может вернуть недопустимое значение. Работа функции от этого не изменится, так как обработка недопустимых значений и есть часть стандартной работы функции, которая заложена в неё изначально. То что ответ будет неверным, не меняет того факта, что функция верно отработала. Это значит только, что писавший программист допустил логическую ошибку. Логические ошибки в принципе нельзя отловить автоматически, по крайне мере пока не изобрели такой нейросети, которая будет предвосхищать человеческие идеи и находить в них логические ошибки.
Это грубая ошибка: тип это не только название свойств и методов, но и семантика поведения. Прикол в том, что LSP, на который Вы ссылаетесь, именно об этом и говорит.
Пусть q(x) является свойством, верным относительно объектов x некоторого типа T.
Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

Нет, не так. Принцип говорит, что в любом месте использования Типа, должна быть возможность использования его Подтипа и при этом не должно возникнуть необходимости менять код вызывающего метода, так чтобы он научился работать с Подтипом. Другими словами вызывающий метод не должен ничего знать о подтипах и у него не должно возникать необходимости явного приведения типа к его подтипу, чтобы работать корректно. Принцип Лисков не накладывает ограничения на реализацию. При этом тип в любом случает будет абстрактен и независим от семантики. Тип не имеет отношение к реализации и семантике. LSP не определяет термин Типа, а только даёт рекомендации «хорошой практики» применения типов. Я сослался на Правило Лисков относительно примера ApeCoder с void Close(), который соответствует этому принципу. В его случае принцип LSP будет соблюдён, так как в обоих случаях Close() описывает финализирующюю операцию, которая будет вызываться Сборщиком Мусора в теле финализирующего метода Dispose() типа IDisposeable (в C#), методом finalize() (в Java), или без сборщика мусора в деструкторе класса (C++), при этом поведение Сборщика Мусора или деструктора не поменяется, вне зависимости закрываете ли вы файл или соединение с базой данных или вообще что-то другое, пишете ли вы в файл или нет. Сборщику Мусора не надо будет знать о существовании подтипа, а только об общем типе (узкий интерфейс IDisposeable, IFinalizeable или что вроде того).
Проще говоря: если есть тип с методом add(x, y), у которого есть свойство возвращать сумму двух чисел, то его нельзя заменить типом с тем же методом, но который возвращает разность двух чисел, несмотря на то, что сигнатуры типов одинаковы.

Нет, не так. Во-первых, вызывающему методу вообще должно быть до лампочки, что ему вернут: сумму или разницу, главное чтобы возвращаемый тип был тот же. Вызывающий метод не должен знать ничего о внутренней логике вызываемого метода. Во-вторых, метод add(x, y) уже возвращает не только сумму, но и разность, при аргументах add(2, -2), так же как метод sub(x, y) будет возвращать сумму при аргументах sub(-2, -2). Если вы назвали метод sub, методом add, то это будет не нарушением LSP, это будет алгоритмическая логическая ошибка (иными словами баг). Даже если метод add(x,y) будет возвращать разность, а не сумму — принцип LSP не будет нарушен, так как нам не придётся переписывать вызывающий метод. Система типов не может (и не должна) отслеживать логические ошибки. Система типов следит только за тем, чтобы вы не могли использовать один тип вместо другого типа (использовать тип интерфейс которого не поддерживается). То есть задача системы типов гарантировать, что в объекте обязательно будет метод add(x,y), а ситуации, что этого метода вдруг не окажется никогда не возникнет. При этом даже, если метод add(x,y) будет реализован с логической ошибкой это не должно повлиять на работу объекта вызывающего этот метод, так как система типов гарантирует, что метод add(x,y) в качестве результата вернёт значение нужного типа.
С чего вы решили, что автор считает что это одно и то же. Из приведенной цитаты этого не следует.

С чего вы решили, что из приведенной цитаты этого не следует?
Метод void Close() и метод void Close() это один и тот же запрос?

Очевидно, что да — по принципу подстановки Барбары Лисков. Реализация не имеет значения, когда речь идёт об абстракциях. Тип относится к уровню абстракций, он независим от реализации. На этом основаны три из шести принципов ООП — инкапсуляция, полиморфизм и абстрагирование. Когда вы проектируете систему в UML диаграммах вас не интересует реализация типов, а только их интерфейс. Все сущности с общим интерфейсом это один тип.
en.wikipedia.org/wiki/Structural_type_system

Википедия недостоверный источник информации. В качестве главного рефернса в этой статье приведено обсуждение на форуме, что как бы уже намекает что статья с запашком. В других указанных источниках нет такого термина. В основном источнике — книге Пирса «Types and Programming Languages» используется правильный термин — параметрический полиморфизм:
According to the first view, type variables should be held abstract during typechecking, thus ensuring that a well-typed term will behave properly no matter what concrete types are later substituted for its type variables. For example, term
λf: X→X. λa:X. f (f a);
has type (X→X) →X→X, and, whenever we replace X by T, the instance
λf:T→T. λa:T. f (f a);
is well typed. Holding type variables abstract in this way leads to the idea of parametric polymorphism, in which type variables are used to encode the fact that a term can be used in many concrete contexts with different concrete types.
©Pierce, Benjamin C. (2002). Types and Programming Languages.


Термина структурная типизация в книге нет (как и термина номинативная типизация), следовательно автор статьи сам выдумал это термин или взял с форума. В одном из источников вообще обсуждается система порождения подтипов, их ковариантность и контравариантность. Ковариантность и контравариантность могут служить правильным термином, но только когда его применяют относительно неявного приведения коллекций. Статья фактически основана ни на чём, там даже предупреждение об этом сверху имеется. Информацию нужно проверять.
В любом случае: во всех достоверных источниках, книгах Кнута, Вирта, Таненбаума, Страуструпа и Хейлсберга за термином структурная типизация стоит совершено другое понятие (value type). Не говоря ещё о C#, где все примитивные типы Int16, Int32, Int64 и т.д — это структуры (struct). Поэтому использования термина структурная типизация в другом контексте не оправдано, двусмысленно и методологически неверно. Правильный термин — полиморфизм. Другой вопрос должны ли типы и подтипы определяться явно (наследование интерфейса) или неявно. Тут есть несколько мнений. Математики утверждают, что типы должны порождаться неявно — как в математике, если значение удовлетворяет абстракции и её аксиомы соблюдены (читай — реализован интерфейс), то его можно свободно использовать — например, вместо действительного числа можно спокойно использовать полином, матрицу или комплексное число, если используемая операция определена для каждого из этих множеств и для операции согласованы аксиомы (ассоциативность, коммуникативность и т.п). Некоторые инженеры утверждают, что программист должен сам явно указать, что объекты одного типа (т.е с общим множеством запросов) могут быть взаимозаменяемы, согласно некой иерархии. Две модели называются явным и неявным порождением подтипов (implicit or explicit subtyping).
Тут нет ни слова про структуру, только про интерфейс. Набор сигнатур не является структурой. Под словом структура и в информатике, и в математике понимают совершено конкретные вещи, которые никак не относятся к набору сигнатур. Когда говорят о структуре подразумевают связи отношений между объектами.
Напротив, тип относится только к интерфейсу объекта — множеству запросов, на которые объект отвечает. У объекта может быть много типов, и объекты разных классов могут иметь один и тот же тип.
© Гамма, Влиссидес, Хелм, Джонсон «Design Patterns»

Тип — это имя, используемое для обозначения конкретного интерфейса. Говорят, что объект имеет тип Window, если он готов принимать запросы на выполнение любых операций, определенных в интерфейсе с именем Window.
© Гамма, Влиссидес, Хелм, Джонсон «Design Patterns»

Если вы создадите ещё один такой же интерфейс с именем View, это будет значить только то, что у вашего типа будет два имени — Window и View. Другими словами вы создадите алиас (кличку) типа, но интерфейс останется тем же. То есть ваш тип будет преставлять из себя два имени, а не одно — тут нет никаких противоречий. Это как например использовать typedef в C/C++ и дать для int алиас number.
С чего вы решили, что автор считает что это одно и то же. Из приведенной цитаты этого не следует.

С чего вы решили, что из приведенной цитаты этого не следует?
Метод void Close() и метод void Close() это один и тот же запрос?

Очевидно, что да — по принципу подстановки Барбары Лисков. Реализация не имеет значения, когда речь идёт об абстракциях. Тип относится к уровню абстракций, он независим от реализации. На этом основаны три из шести принципов ООП — инкапсуляция, полиморфизм и абстрагирование. Когда вы проектируете систему в UML диаграммах вас не интересует реализация типов, а только их интерфейс. Все сущности с общим интерфейсом это один тип.
en.wikipedia.org/wiki/Structural_type_system

Википедия недостоверный источник информации. В качестве главного рефернса в этой статье приведено обсуждение на форуме, что как бы уже намекает что статья с запашком. В других указанных источниках нет такого термина. В основном источнике — книге Пирса «Types and Programming Languages» используется правильный термин — параметрический полиморфизм:
According to the first view, type variables should be held abstract during typechecking, thus ensuring that a well-typed term will behave properly no matter what concrete types are later substituted for its type variables. For example, term
λf: X→X. λa:X. f (f a);
has type (X→X) →X→X, and, whenever we replace X by T, the instance
λf:T→T. λa:T. f (f a);
is well typed. Holding type variables abstract in this way leads to the idea of parametric polymorphism, in which type variables are used to encode the fact that a term can be used in many concrete contexts with different concrete types.
©Pierce, Benjamin C. (2002). Types and Programming Languages.

Термина структурная типизация в книге нет (как и термина номинативная типизация), следовательно автор статьи сам выдумал это термин или взял с форума. В одном из источников вообще обсуждается система порождения подтипов, их ковариантность и контравариантность. Ковариантность и контравариантность могут служить правильным термином, но только когда его применяют относительно неявного приведения коллекций. Статья фактически основана ни на чём, там даже предупреждение об этом сверху имеется. Информацию нужно проверять.
В любом случае: во всех достоверных источниках, книгах Кнута, Вирта, Таненбаума, Страуструпа и Хейлсберга за термином структурная типизация стоит совершено другое понятие (value type). Не говоря ещё о C#, где все примитивные типы Int16, Int32, Int64 и т.д — это структуры (struct). Поэтому использования термина структурная типизация в другом контексте не оправдано, двусмысленно и методологически неверно. Правильный термин — полиморфизм. Другой вопрос должны ли типы и подтипы определяться явно (наследование интерфейса) или неявно. Тут есть несколько мнений. Математики утверждают, что типы должны порождаться неявно — как в математике, если значение удовлетворяет абстракции и её аксиомы соблюдены (читай — реализован интерфейс), то его можно свободно использовать — например, вместо действительного числа можно спокойно использовать полином, матрицу или комплексное число, если используемая операция определена для каждого из этих множеств и для операции согласованы аксиомы (ассоциативность, коммуникативность и т.п). Некоторые инженеры утверждают, что программист должен сам явно указать, что объекты одного типа (т.е с общим множеством запросов) могут быть взаимозаменяемы, согласно некой иерархии. Две модели называются явным и неявным порождением подтипов (implicit or explicit subtyping).
То-есть, если у меня есть вот такие классы. То для компилятора (или среды исполнения) они будут разными типами.

Нет, это не правда. Проблема вашего непонимания в том, что вы говорите о Классах и Типах как об одном и том же. Но на самом деле Класс и Тип это два совершенно разных понятия. В C# вы определяете именно Классы, а в Typescript вы определяете Типы. Вот что об это пишет легендарная Банда Четырёх (Гамма, Влиссидес, Хелм, Джонсон) в Библии программиста «Design Patterns»:
Важно понимать различие между классом объекта и его типом.
Класс объекта определяет, как объект реализован, то есть внутреннее состояние
и реализацию операций объекта. Напротив, тип относится только к интерфейсу объекта — множеству запросов, на которые объект отвечает. У объекта может быть
много типов, и объекты разных классов могут иметь один и тот же тип.
Разумеется, между классом и типом есть тесная связь. Поскольку класс определяет, какие операции может выполнять объект, то заодно он определяет и его
тип. Когда мы говорим «объект является экземпляром класса», то подразумеваем,
что он поддерживает интерфейс, определяемый этим классом.
© Гамма, Влиссидес, Хелм, Джонсон

Это значит, что термин Тип относится только к множеству запросов, которые можно отправить объекту. Если множество запросов совпадает — значит у объектов Тип будет общим, независимо от того к какому Классу эти объекты принадлежат.
Тип — это имя, используемое для обозначения конкретного интерфейса. Говорят, что объект имеет тип Window, если он готов принимать запросы на выполнение любых операций, определенных в интерфейсе с именем Window. У одного
объекта может быть много типов. Напротив, сильно отличающиеся объекты могут разделять общий тип. Часть интерфейса объекта может быть охарактеризована одним типом, а часть — другим. Два объекта одного и того же типа должны разделять только часть своих интерфейсов.
© Гамма, Влиссидес, Хелм, Джонсон

Поэтому тот факт, что ты можешь использовать Типы с разными именами и общим интерфейсом в одной и той же функции не противоречит тому факту, что ЯП является строго типизированным — ведь это один и тот же Тип согласно всем правилам. Ты ожидал получить строку и в результате получил строку — ни целое число, ни число с плавающей точкой, ни адрес в памяти. Следовательно, типизация строгая — какой интерфейс просил, такой и получил. Это и называется строго типизированный ЯП. Класс и Тип это разные сущности. Если вы в вашем примере на C# будете использовать Интерфейсы вместо Классов, то получите тот же результат, что и в Typescript. По этой же причине нет такого понятия, как «Приведение Классов», а только «Приведение Типов» — потому что объект может иметь только один Класс, но Типов у объекта может быть сколько угодно. Объект не может поменять свой Класс, даже если вы приведёте его к Типу родительского Класса, то он всё равно не изменит свою внутреннюю реализацию (т.е Класс), а только усечёт свой интерфейс (т.е Тип). Тут нет противоречия со строгой типизацией, потому что и у дочернего и у родительского Класса — один Тип. Это вторая парадигма ООП — полиморфизм (один класс => множество типов).
А вот такая вот дичь, от «пацанов на работе»:
Пацаны объяснили мне, что у ts статическая типизация, но не номинативная.

Полная чушь. Нет таких понятий. А структурная типизация это вообще совершено другое. Структурные типы (или же значимые тип — value type) это типы, которое передаются по значению, а не по ссылке. Не слушайте всяких пацанов. Если хотите узнать достоверную информацию, а не очередной бред, читайте книги уважаемых авторов, учёных со степенями в информатике и математике, таких как Банда Четырёх, Дональд Кнут «Искусство программирования», Стивен Макконнелл «Code Complete», книги Вирта и Таненбаума. Там очень подробно разъясняются все эти вопросы.
При этом все статически типизированные ЯП, которые я использовал, дают возможности для динамической типизации. Any в TS, Dynamic в сишарпе. В конце концов тот же тип Object — по идее, я запихиваю в него все что угодно, и говорю, что у меня статически типизированный код. Но строго говоря, это не совсем так.

Динамическая типизация это не когда «я запихиваю в него все что угодно», а когда Тип определяется во время исполнения. Object никак нельзя назвать динамической типизацией, так как он определяется на этапе компиляции. Вы сами об этом же писали чуть выше, а потом сразу же ниже написали глупость. Более того динамическая типизация тоже может быть строй, это уже детали реализации компилятора и среды выполнения.
А вообще нужно в первую очередь понимать, что Типы Данных это всего лишь абстракция. На уровне процессора или даже чуть более высоком уровне языка Ассеблера нет никаких типов. У процессора есть только один тип — машинное слово (можно разделить на байты, слово, двойное слово, но это не суть). То что на языке Ассеблера нет типов совсем не делает его «игрушечным языком». Типы нужны исключительно для удобства и избавления от многих раздражающих случайных ошибок вроде «сложил букву и число», «умножил адресс на строку» или «записал в регистр грязные биты». Любой ЯП это просто текст в файлике, который так или иначе в итоге станет машинным кодом. Нет «серьёзных» и «несерьёзных» языков программирования, не важно где исполняется твой «текст» в браузере, интерпретаторе, виртуальной машине или сохранится в бинарник. За 7 лет рабочего опыта пора уже отучится от таких детских взглядов на программирование и код. Ладно джуны нифига не понимают и только спорят, чей папка круче, Java или C#. Ну сениору уже пора бы более трезво смотреть на вещи.
И для меня тогда важными и крутыми программистами были Бьорн Страуструп и Деннис Ритчи. В меньшей степени Линус Торвальдс, Билл Гейтс или Стив Возняк.

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

Тут не соглашусь. На деле создание языка программирования не столь сложная задача. В наше время новые ЯП-ы (особенно интерпретируемые) появляются каждый год в большом количестве, растут как грибы после дождя. Rust, Ceylon, Dart, Kotlin, Red, Opa, Elixir, Elm, Julia, P, TypeScript, PureScript, Hopscotch, Cuneiform, Crystal, Hack, Swift, Raku, Reason, Ballerina, Bosque, Citrine — это лишь малая часть языков появившихся за последние 10 лет. И многими инженерами очень справедливо поставлен вопрос: «А была ли необходимость в этих языках? Разве это не изобретение велосипедов?». С инженерной точки зрения компилятор довольно простая программа — преобразователь текстовых документов, парсер, лексер, генератор кодов. Оптимизация компилятора задача скорее нудная и трудоёмкая, чем сложная. Создание синтаксиса языка — не инженерная задача, а теоретическая абстрактная задача математического толка. Другой вопрос насколько созданный язык будет удобен, выразителен, актуален и идейно оправдан. Принесёт ли этот язык какие нибудь новые подходы, парадигмы? Откроет ли новые выразительные возможности, которые позволят эффективнее и быстрее решать определённый кластер задач? Написание большой разветвлённой информационной системы на языке программирования намного более сложная задача, чем написать сам язык программирования. Я сам писал компиляторы и интерпретаторы, хотя и для уже существующих спецификаций и синтаксисов. Думаю осилил бы и создание своего интерпретируемого языка. А вот написать свою ОС (или хотя бы ядро) — это уже действительно вершина карьеры софтверного инженера. В этом плане Деннис Ритчи с Unix и Линус Торвальдс с Linix превзошли в крутости других перечисленных мастодонтов сферы, Страуструп рядом не стоит (хотя есть ребята типа Ville Turjanmaa, способные написать не то что ядро, а ОС целиком на языке Ассемблера).
Забавно выходит — ты сокрушаешься, что бизнесмены создающие приложения пользовательского уровня получают все лавры и боготворишь создателей ЯПов как достигших «вершины карьеры» в софтинженерии. Я сокрушаюсь, что все лавры получают создатели языков программирования, когда как создатели намного более сложных информационных систем, рассматриваются как просто пользователи ЯП, хотя решаемые ими задачи в сотни раз масштабнее и грандиознее обычного ЯП-а. А где-то в мире наверняка есть человек, который сокрушается, что я боготворю создателей ОС, когда есть инженеры разрабатывающие распределенные научно-вычислительные системы на основе суперкомпьютеров проекта Top500, имен которых не знает вообще никто. «Всегда есть более крупная рыба.»

Information

Rating
Does not participate
Registered
Activity