Поскольку я с Немерле знаком слабо, то задам вопрос: можно ли добавить сюда описание новой операции (допустим — возведение в квадрат, Sqr) без перетрансляции исходного текста Expr?
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Поскольку я с Немерле знаком слабо, то задам вопрос: можно ли добавить сюда описание новой операции (допустим — возведение в квадрат, Sqr) без перетрансляции исходного текста Expr?
Вопрос на который трудно дать однозначный ответ.
С одной стороны ответ будет "нет и не надо". Варинты средство рассчитанное на статическую типизацию и поддержку компилятора. Весь его кайф в том, что динамическое расширение им противопаказано.
С другой стороны ответ будет "да", так как мы всегда можем создать динамическое решение другими средствами. Мы можем сделать так:
| Call(name, parms) as call =>
def func = funcMap(call);
if (func == null)throw SomeException();
else func(makeParams(parms));
Конечно в реальном ЯП функции не захардкожены. Однако мест где применяются сложные паттерны много.
Более того в Немерле квази-циритование конвертируется в список PExpr, а это как раз варинт отражающий нетипизированное АСТ. Это позволяет в качестве образца использовать просто код. То есть мы можем написать:
match (expr)
{
| <[ if ($x) $y else $z ]> => // используем одвыражения if-а
}
а if, между прочем, это макрос.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
ГВ>>Поскольку я с Немерле знаком слабо, то задам вопрос: можно ли добавить сюда описание новой операции (допустим — возведение в квадрат, Sqr) без перетрансляции исходного текста Expr?
VD>Вопрос на который трудно дать однозначный ответ. VD>С одной стороны ответ будет "нет и не надо". Варинты средство рассчитанное на статическую типизацию и поддержку компилятора. Весь его кайф в том, что динамическое расширение им противопаказано.
Ясно. В принципе, чего-то в таком духе я и ожидал. То есть для создания набора, расширяемого в runtime варианты, очевидно, не годятся. В отличие от иерархий типов и публичных контрактов, по отношению к которым, кстати, и сформулирован LSP. Во избежание терминологической путаницы замечу, что здесь под термином "контракт" я понимаю набор соглашений об использовании типа: протокол, инварианты и т.п. вне зависимости от того, описаны контракты формально средствами целевого языка или нет.
VD>С другой стороны ответ будет "да", [...] VD>Конечно в реальном ЯП функции не захардкожены. Однако мест где применяются сложные паттерны много.
Тут тоже понятно — обратиться к функции по имени.
VD>Более того в Немерле квази-циритование конвертируется в список PExpr, а это как раз варинт отражающий нетипизированное АСТ. [...] VD>а if, между прочем, это макрос.
Здесь тоже ясно, но это уже из области DSL-ей, которые есть смежная область.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>>>>>>>[...]Если один фрагмент — не беда, если десяток, то... IT>>>>>>И в чём именно проблема? ГВ>>>>>Только в количестве. IT>>>>Ну так функционал всё равно писать надо, много его или мало. ГВ>>>А кто-то это отрицает? IT>>Так в чём тогда проблема? ГВ>В расширяемости, разумеется.
Наример? И как эта пробема решается другими средствами?
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Дм.Григорьев, Вы писали:
IB>>>А между слоями и модулями контрактов нет? IT>>В том смысле в котором они понимаются теми, кто ввёл этот термин — нет.
ДГ>Имеется в виду наличие дополнительной документации (UML, etc.) вне кода? Или что?
Имеется ввиду вариант определения контрактов введённый в SOA.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IB, Вы писали:
IT>>В том смысле в котором они понимаются теми, кто ввёл этот термин — нет. IB>Разьве? Ну ладно, не суть...
IT>> Только нафига тогда нужен такой термин, без которого до этого прекрасно обходились и не было никакой путаницы. IB>Ну хорошо, замени контракт на "публичный интерфейс модуля, ect.." суть-то от этого не изменится.
Конечно же измениться. Как минимум я не буду это отражать на веб-сервисамы, SOA и WCF и мы сразу начнём говорить на одном языке
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>>>Так в чём тогда проблема? ГВ>>В расширяемости, разумеется.
IT>Наример?
А что тут не понятного? Когда конкретный вызов метода проводится на основании явного анализа типа объекта, то для добавления нового типа нужно модифицировать все места программы, где это перечисление имеется. Будут это switch/case, цепочки if или выражения для pattern-matching — не важно.
IT>И как эта пробема решается другими средствами?
Я тебе должен рассказывать про наследование и фабрики? Игорь, я точно не с твоим двойником общаюсь?
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>Но при этом возможна забавная коллизия, когда с точки зрения компилятора типы находятся в отношении "супертип-подтип", а с точки ГВ>зрения LSP — нет.
Да, давно где то встречал (источник вспомнить не могу) пример иллюстрирующий LSP и его нарушение, с фразой:
Здравствуйте, Геннадий Васильев, Вы писали:
IT>>И как эта пробема решается другими средствами?
ГВ>Я тебе должен рассказывать про наследование и фабрики? Игорь, я точно не с твоим двойником общаюсь?
Наверное имеется ввиду что во многих реальных и более примитивных случаев усложнение логики не окупается и бывает проще, выгоднее и нагляднее использовать switch или else if.
Хотя одно время пришлось сотрудничать с человекам который в 3х классах написал многострочный if instanceof ... (порядка 30 типов — расширяемый набор виджетов). И при добавлении нового виджета приходилось модифицировать все три класса добавляя if instanceof.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, VladD2, Вы писали:
VD>>В общем, чтобы написать калькулятор на Немерле с использованием паттерн-матчинга и алгеброических типов у меня ушло 10 минут. Вот полный его код: VD>>
VD>>public variant Expr
VD>>{
VD>> | Literal { value : double; }
VD>> | Call { name : string; parms : list[Expr]; }
VD>> | Plus { expr1 : Expr; expr2 : Expr; }
VD>> | Minus { expr1 : Expr; expr2 : Expr; }
VD>> | Mul { expr1 : Expr; expr2 : Expr; }
VD>> | Div { expr1 : Expr; expr2 : Expr; }
VD>> public override ToString() : string
VD>> {
VD>> match (this)
VD>> {
VD>> | Literal(value) => value.ToString()
VD>> | Call(name, parms) => $"$name($(parms.Map(_.ToString()).ToString(\", \")))"
VD>> | Plus(e1, e2) with op = "+" | Minus(e1, e2) with op = "-"
VD>> | Mul(e1, e2) with op = "*" | Div(e1, e2) with op = "/" => $"$e1 $op $e2"
VD>> }
VD>> }
VD>>
R>До боли что-то напоминает... А! Это же старый добрый С! Уж не так ли мы писали в течение 20 лет:
R>
R>Имхо тождественно с точностью до синтаксиса.
Неправильно, в вариантах каждый подтип имеет свои данные, тут у тебя type и data разделены (и вообще говоря для какого типа верны какие данные остается только догадываться) и ничто не мешает тебе для Literalа попробовать взять name, что есть UB.
R> Практически так же лаконично.
Ну не совсем, да и ошибки не контролируются как я уже сказал.
R>И уж не от этого ли мы бежали? Уж не это ли у нас было плохо поддерживаемым, error-prone и т.д? И уж не глядя на это ли мы решили, что хорошо было бы иметь более структурированный код и более явно выделять сущности (пустай даже ценой менее лаконичного кода)? R>
R>Нет, я согласен, для этого конкретного примера код на C# выглядит как overkill. Но в начале поста пример-то у тебя был другой — несколько типов деревьев, куча типов нодов и т.д. R>В С, кстати, "умные" компиляторы или тулзы тоже выдают варнинг на "неполные" switch'и. Но тем не менее такой подход мировым сообществом программистов был признан... не очень хорошим.
Он хорош для вот таких простых случаев. Каждой задаче — свой подход. Зачем городить зоопарк виртуальных функций для простейших операций?
R>Твой "switch" выглядит красивым и лаконичным и читаемым пока он такой маленький и не распух. А представь у тебя будет на каждый case по 10-50 строк кода (разное поведение в зависимости от настроек, логирование и т.д. а в промышленном проекте так и будет).
Правильно — для каждой задачи свой подход. К слову, в плане реализации вариантов в Немерле конкретные типы варианта — это как раз подклассы, более того, их можно отдельно создавать
def plus = Expr.Plus( expr1, expr2 )
и передавать в функции
public ProcessPlus( expr: Expr.Plus )
а сам вариант также может наследоваться от другого класса (правда от самого варианта наследовать нельзя).
R>И уже пошли возможности для ошибок — а что если я и для Plus и для Minus вызову одну функцию?
Ну а если ты для реализации виртуальной функции в подклассах Plus и Minus скопипастишь код ? Это даже труднее будет найти, поскольку код разнесен.
R>Или вот ещё аналогичный пример. Допустим есть некая функция. Пока относительно небольшая, она выграет от того, что мы её не будем разделять на несколько — одна функция из 10 строк — просто, читаемо, можно охватить логику одним взглядом. Если же функцию из 10 строк разбить на 3, то во-первых, кол-во кода существенно вырастет, во-вторых станет менее читаемо, т.к. не так локально.
Во-во.
R>Когда же есть функция из 100 строк, то она уже будет проигрывать от того, что она реализована одной функцией на 7 экранов, т.к. она уже все равно не простая и не лаконичная и одним взглядом её не поймёшь. Тут она уже выграет от разбивания на несколько функций, т.к. код станет более структурированным, более чётко будут видны интерфейсы и зависимости, от некоторых частей можно будет абстрагироваться и т.д. При этом кол-во кода вырастет — но мы готовы с этим мириться, т.к. взамен получаем более важные вещи.
R>Так же и с ООП. Для небольших задач оно может показаться overkill, одна для больших и сложных задач лучше написать больше кода, но зато явно выделить все понятия, разложить всё по полочкам. Вобщем структурировать. Допустим есть базовый класс Expression c 5 виртуальными функциями и есть 147 производных классов. Что бы всё это понять достаточно изучить базовый класс и один наследник. Всё. далее, когда надо будет допустим изменить поведение "сложения" понятно, что надо найти некий ExpressionPlus и изменить его.
Да оно понятно, проблема в том что код многомерен, и что где-то удобнее написать в одной функции рядом действия для всех подклассов (как написано выше), а где-то определить одно и то же действие во всех классах.
R>Вобщем, к чему я это всё. ООП предоставляет очень мощные инструменты для создания больших проектов. Возможно его зачастую не к месту используют для решения таких задач как калькулятор выше — это да, согласен. Но тем не мене такие понятия как абстракции, ортогональность — сила.
Правильно. Авторы Немерле и не призывают от них отказываться (все что есть в C# есть и там). Но для некоторых случаев они припасли кое-что поудобнее .
R>Проблема, что кода много и он "по копирке" существует. Но есть приёмы, которыми практически всегда можно свести кол-во "повторяющегося" кода к минимуму.
Не всегда их удается изящно применить.
R>Если, конечно, захотеть это сделать.
R>
Здравствуйте, VladD2, Вы писали:
VD>Вообще в ООП есть много догм. Одна из них обязательная инкапсуляция. ООП почему-то не хочет воспринимать чистые данные.
На самом деле чистые данные — это объект типа Tuple
Здравствуйте, Андрей Хропов, Вы писали:
АХ>На самом деле чистые данные — это объект типа Tuple
Под чистыми данными я имел в виду объекты без методов. Или даже так. Объекты с небольшим набором методов. Главное что они предназначены для обработки другими фукнциями. Такие объекты зачастую являются неизменяемыми (imutable).
Собственно это никак не противоречит ООП. Это всего лишь противоречит некоторым докамтическим приципам.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Геннадий Васильев, Вы писали:
ГВ>А что тут не понятного? Когда конкретный вызов метода проводится на основании явного анализа типа объекта, то для добавления нового типа нужно модифицировать все места программы, где это перечисление имеется.
Может писать такой и не удобно. Зато читать очень хорошо. Вся логика конкретного функционала находится в одном месте. Не надо прыгать по разным файлам и классам, чтобы получить полную картину происходящего.
ГВ>Будут это switch/case, цепочки if или выражения для pattern-matching — не важно.
Только switch и if не надо, пожалуйста. Ценность подхода уменьшается пропорционально количеству порождаемого им мусора. К сожалению, в решениях со switch и if из-за деревьев становится леса не видно.
IT>>И как эта пробема решается другими средствами?
ГВ>Я тебе должен рассказывать про наследование и фабрики? Игорь, я точно не с твоим двойником общаюсь?
При чем тут фабрики и двойники? Твоя альтернатива — это наследование и инкапсуляция. Так и скажи.
Разница лишь в том, что в случае с объектами функционал скапливается вокруг объектов. В случае с match функционал помещается в одном месте, вокруг одной функциональной единицы приложения. У каждого подхода есть свои достоинства и недостатки, каждый может быть плох или хорош в каждом конкретном случае. Следовательно, не важно сколько таких мест в программе. Если разумнее выносить функционал в такие методы, то так и надо делать. Если его лучше оставить в объекте, то никто этого делать не запрещает.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>У каждого подхода есть свои достоинства и недостатки, каждый может быть плох или хорош в каждом конкретном случае. Следовательно, не важно сколько таких мест в программе. Если разумнее выносить функционал в такие методы, то так и надо делать. Если его лучше оставить в объекте, то никто этого делать не запрещает.
Так я подразумеваю как раз только те ситуации, когда предполагается расширение набора типов. Или проблема с расширением может возникнуть в условиях, когда набор типов зафиксирован? Это такой оксюморон, да? Так что не надо излишне обобщать. "В общем" понятно, что каждому инструменту своё место. Просто LSP, которой здесь онтопик, лучше всего применим именно тогда, когда мы предполагаем расширять набор типов. Отсюда уже формулируются "супертипы", "подтипы" и всё прочее подобное. И тогда решения, построенные на явном перечислении допустимых типов могут (я не говорю — непременно должны) оказаться неприемлемыми.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, VladD2, Вы писали:
ГВ>>Не важно, есть pattern-matching в языке, или нет. Проблема всегда в том, сколько именно такого кода появится в программе. Если один фрагмент — не беда, если десяток, то...
VD>Конечно! Разница только в удобстве! Вот только получая это удосбтво начинаешь задумываться на тем не являются ли догмами многие "незыблимые принципы ООП".
Да нет, принципы ООП догмами вовсе не являются, ну разве что в неокрепших умах, кои склонны к догматизму. И никто, вроде бы, на их догматичности не настаивает (прежде всего — сами авторы).
Собственно, тот же самый LSP вполне можно нарушать по отношению к классам (да и по отношению к функциям, если мы проецируем его на понятие функционального типа). Да, эти нарушения приведут ко вполне определённым послествиям. Но кто сказал, что эти последствия непременно фатальны? Семь вёрст, — как говорится, — не крюк.
VD>На самом деле есть два подхода. Подход от функции и подход от объекта. Иногда удобен одни. Иногда другой. Подход от функции позволяет расширять функциональность не меняя классов. Это бывает очень полезно. Как я уже говорил, паттерн Посетитель решает ту же задачу, только очень убого.
Ещё есть подход от данных и программ. Базовый, в общем-то. Остальное зависит уже от того, какой способ группировки мы выбираем в качестве строительного материала для абстракций.
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, IB, Вы писали:
VD>> Я сказал, что он выглядит натянуто если расширить свой кругозор и рассматривать не только ООП, а и другие подходы тоже. IB>Я же сказал, что этот принцип совершенно не зависит от подхода, если забыть про ООП и расширить его на контракты и реализации.
Иван, забей. Ты, имхо, пытаешься уж слишком углубиться в дебри высоких абстракций. Эдак мы доберемся до двуединства Инь и Янь и их достаточности для описания всего сущего. К сожалению, из такой безусловно справедливой модели не удастся извлечь ничего, мало-мальски практически пригодного. Ну вот в принципе ничто не запрещает нам рассматривать вселенную как материальную точку с массой около 10^58 грамма, которая ни с чем не взаимодействует. Ну и что?
Так и с принципом Б.Л. Во-первых, изначально он был высказан именно в рамках ООП и именно по отношению к типам. Нет, я конечно в курсе, что в математике есть масса теорем, которые применяются значительно шире, чем мыслили их авторы. Но попытка интерпретировать принцип подстановки в общефилосовском смысле нас далеко не приведет. Да, можно считать, что если заменить утку на скотном дворе произвольным объектом, который жрет отруби, крякает, и несет яйца с потребной периодичностью, мы можем смело считать это чудо уткой. Даже если у него крыльев нету вовсе, а яйцекладов, к примеру, два. Контракт по отрубям, кряканью и яйцам выполнил — всё, свободен, иди окукливайся. Барбара разрешила.
Но вот, к примеру, в ФП уже не удастся найти что-то похожее на Контракт. Сигнатура функции служить таковым не может — если мы заменим Min на Max, то извините, последствия будут весьма и весьма заметны. А если определить контракт функции Min как возврат того аргумента, который не больше оставшегося, то заменить ее ничем вовсе не удастся.
Типы в ФП тоже вряд ли будут себя вести "правильным" с точки зрения Барбары образом. Потому, что, к примеру, целое число нельзя заменить комплексным — извлечение корня из целой -1 даст совсем другой результат, чем из комплексной. Да и в обратную сторону тоже нельзя. А если подойти с должной строгостью, то даже пытаться применить принцип подстановки к ним нельзя — в терминологии ФП экземпляры типов вовсе не имеют никакого поведения, стало быть и сравнивать нечего.
1.2.0 alpha rev. 655
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Sinclair wrote: > Но вот, к примеру, в ФП уже не удастся найти что-то похожее на Контракт.
Ну почему же. Например, возьмем, к примеру, обычную высшую алгебру. У
нас есть полугруппа, моноид, группа, кольцо, тело, поле. При этом, все
теоремы для полугруппы будут выполняться, если вместо нее взять группу,
или, например, алгебраическую структуру с одной из операций поля.
То есть, контракты можно представить в виде набора аксиом. Для того же
sqrt у нас есть аксиома "sqrt(x)*sqrt(x)=x". Поэтому любой объект,
который выполняет эти аксиомы будет нам подходить. Например, мы можем
взять sqrt, использующий метод аппроксимаций Ньютона или fastsqrt из
сырцов Q3, а может даже sqrt, использующий статистические вычисления.
Здравствуйте, Cyberax, Вы писали: C>Ну почему же. Например, возьмем, к примеру, обычную высшую алгебру. C>нас есть полугруппа, моноид, группа, кольцо, тело, поле. При этом, все C>теоремы для полугруппы будут выполняться, если вместо нее взять группу, C>или, например, алгебраическую структуру с одной из операций поля.
Я себе совершенно не представляю, что означает "взять вместо полугруппы группу".
C>То есть, контракты можно представить в виде набора аксиом. Для того же C>sqrt у нас есть аксиома "sqrt(x)*sqrt(x)=x". Поэтому любой объект,
О! Объект. Что есть объект? В ФП объектов нет. Есть функции, есть структуры. Объект в ООП является сущностью, обладающей идентичностью, поведением, и состоянием.
Поэтому для моделирования математики в ООП приходится идти на трюки. Вводить объекты типа "математика". Если требуется, к примеру, вычислять производную, то значит придется вводить объект "функция", и объект "дифференциатор", возвращающий для каждой функции другую функцию — ее производную. И весь полиморфизм сводится к тому, что можно заменять "дифференциатор" каким-то другим. С точки зрения математики, большого смысла это не имеет, т.к. существует ровно один способ получать из функции производную, который собственно зашит в ее определение. С точки зрения императивного программирования можно иметь разные "дифференциаторы", возвращающие различные, но эквивалентные производные функции.
Ну или можно отнаследовать от "дифференциатора" объект "функциональный анализ", который умеет в дополнение к дифференцированию выполнять еще и интегрирование. О да, вот этот объект будет потомком в смысле Лисков — ведь его можно отдавать всем клиентам, которым нужно дифференцирование.
Практического смысла это выпиливание по вазелину не имеет, поэтому во всех современных ОО-библиотеках есть банальный синглтон Math, который реализует чуть более сложные вычисления, чем арифметика.
C>который выполняет эти аксиомы будет нам подходить. Например, мы можем C>взять sqrt, использующий метод аппроксимаций Ньютона или fastsqrt из C>сырцов Q3, а может даже sqrt, использующий статистические вычисления.
А, пардон, зачем? И к чему это приведет? Напомню, что Барбара выдвигает требования к классам объектов. Что, мы сможем благодаря этому доказать, что одна из функций sqrt является в каком-то смысле наследником другой? Нет, не сможем. А уж если fastsqrt вернет неточный результат, то это вообще испортит нам всю малину — ведь мы сможем таки отличить в клиенте "настоящий" sqrt и переданный нам эрзац. Нарушаться будет соотношение sqrt(x)^2 == x.
Ок, ну, наверное, единственное что можно сделать в ФП в стиле Лисков — это доопределить функцию, которая ранее работала с неким множеством, на более широком множестве. Ну как там определяют Г(x), которая для натуральных x сводится к факториалу, но в отличие от факториала определена на всех комплексных числах. Наверное, так.
1.2.0 alpha rev. 655
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Геннадий Васильев, Вы писали:
IT>>У каждого подхода есть свои достоинства и недостатки, каждый может быть плох или хорош в каждом конкретном случае. Следовательно, не важно сколько таких мест в программе. Если разумнее выносить функционал в такие методы, то так и надо делать. Если его лучше оставить в объекте, то никто этого делать не запрещает.
ГВ>Так я подразумеваю как раз только те ситуации, когда предполагается расширение набора типов. Или проблема с расширением может возникнуть в условиях, когда набор типов зафиксирован? Это такой оксюморон, да? Так что не надо излишне обобщать. "В общем" понятно, что каждому инструменту своё место. Просто LSP, которой здесь онтопик, лучше всего применим именно тогда, когда мы предполагаем расширять набор типов. Отсюда уже формулируются "супертипы", "подтипы" и всё прочее подобное. И тогда решения, построенные на явном перечислении допустимых типов могут (я не говорю — непременно должны) оказаться неприемлемыми.
Это всё, Гена, надо было говорить в своём первом сообщении
Здравствуйте, Sinclair, Вы писали:
S>Но вот, к примеру, в ФП уже не удастся найти что-то похожее на Контракт. Сигнатура функции служить таковым не может — если мы заменим Min на Max, то извините, последствия будут весьма и весьма заметны. А если определить контракт функции Min как возврат того аргумента, который не больше оставшегося, то заменить ее ничем вовсе не удастся. S>Типы в ФП тоже вряд ли будут себя вести "правильным" с точки зрения Барбары образом. Потому, что, к примеру, целое число нельзя заменить комплексным — извлечение корня из целой -1 даст совсем другой результат, чем из комплексной. Да и в обратную сторону тоже нельзя. А если подойти с должной строгостью, то даже пытаться применить принцип подстановки к ним нельзя — в терминологии ФП экземпляры типов вовсе не имеют никакого поведения, стало быть и сравнивать нечего.
Понятие контрактов вполне применимо в ФП. Смущает отсутствие классов/интерфейсов? Их можно заменить модулями ML или классами типов Haskell.
Контракт в ФП — это логическое утверждение об алгебраических свойствах функции или семейства функций. Например, применив функцию reverse дважды, мы должны получить исходный список. Функция сравнения, передаваемая в функцию сортировки, должна быть рефлексивной, транзитивной и антисимметричной. Известный пример контракта в функциональном мире — законы монад.
1. (return x) >>= f == f x
2. m >>= return == m
3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)
Что замечательно, для тестирования на соответствие контрактам есть автоматизированные средства — см. QuickCheck, реализованный для Haskell, Erlang, Python, etc.