[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: programming languages



Evening, Andrei. 

Andrei Sosnin <demonsly@hot.ee> 22:29 13/1/2003 wrote:

>> Да. Проблемы начинаются тогда, когда поведение addvaluesbyone для MyClass и
>> ConcreteSuperclassOfMyClass - различно (такое очень часто пишут
>> горе-програмисты при наследовании, т.е. реализуют в вирт. методе поведение,
>> нарушающее семантику, заложенную в предка). Что приводит к трудноловимым
>> ошибкам и костылям.

 AS> Если следовать нормальной логике, то ничего такого не будет. 
 AS> Сомневаюсь, что программисты ФП намного сильнее защищены от таких
 AS> "дураков", бишь от глупых вещей. Здесь не спасет строгая типизация или
 AS> что-либо еще. В конце концов, тот же горе-программист может вместо
А вот поди ж ты - спасает :)

 AS> направления строки на стандартный поток вывода (в С++ - cout) сделать
 AS> вывод на стандартный поток ошибок и потом долго чесать голову, почему
 AS> же у него не происходит кеширования вывода и в результате программа
 AS> работает в два раза медленнее, чем должна...
Я говорю об ошибках менее тривиальных, возникающих, как правило, при
доработке/переработке существующего кода, расширении ф-сти существующих
(библиотечных) классов. Причем чаще всего такие ошибки делаются ненамерено.

То, что я пытался проиллюстрировать примером, я считаю одним из самых
больших идейных "пробоев" большинства реализаций парадигмы OOP - наследники
не обязаны сохранять инвариант поведения предка. Очень часто допускаемый
ляп выглядит так: у тебя есть (библиотечный) класс SortedList. Ты пишешь
код, который полагается на то, что список сортированый. Потом ты наследуешь
от SortedList какой-нибудь MySortedList, заменяя в нем, допустим,
внутреннюю струкуру хранения данных со списка на список + avl-дерево. Ты
переписываешь методы работы с данными, но не все. В результате у тебя
получается класс, поведение которого относительно унаследованого
виртуального метода, (например, next()) отличается от поведение предка.
Чаще всего такое получается по недосмотру. Тем не менее, в большинстве (во
всех?) реализаций OOP ты можешь сунуть MySortedList в любое место, где
уместен SortedList. Поведение наследника отличается => отличается и
поведение кода, который работает с ним. И разработчик собирается в
очередной поход против ветряных мельниц.... С моей точки зрения,
большинство т.н. design pattern из OOP являются борьбой этой и подобными
"идеологическими недоработками" (см. например
http://norvig.com/design-patterns/) 

В качестве иллюстрации, как может быть по-другому, пример из одного из ФЯ:
описывается класс типов SortedList. Указывается, что произвольный _тип_
может рассматриваться как SortedList, если для него определены следующие
операции: ... Если это возможно, ты можешь указывать реализации операций в
терминах других операций из списка определенных для данного класса типов. 

Пример (класс типов, для которых определено понятие эквивалентности):

class Eq a where
    (==), (/=) :: a -> a -> Bool

    -- Minimal complete definition: (==) or (/=)
    x == y      = not (x/=y)
    x /= y      = not (x==y)

После этого я могу указать, как именно любой user-defined тип можно
отнести к определенному классу типов. Для этого я даю определение операций
(возможно, не всех, если часть из них выражается друг через друга):

instance Eq MyType where
  a `==` b   =  myCompare a b
  определени для (/=) будет построено автоматически.

Таким образом я группирую произвольные типы по их поведению. Классы можно
организовывать в иерархии:

class (Eq a) => Ord a where
    compare                :: a -> a -> Ordering
    (<), (<=), (>=), (>)   :: a -> a -> Bool
    max, min               :: a -> a -> a

    -- Minimal complete definition: (<=) or compare
    -- using compare can be more efficient for complex types
    compare x y | x==y      = EQ
                | x<=y      = LT
                | otherwise = GT

    x <= y                  = compare x y /= GT
    x <  y                  = compare x y == LT
    x >= y                  = compare x y /= LT
    x >  y                  = compare x y == GT

    max x y   | x <= y      = y
              | otherwise   = x
    min x y   | x <= y      = x
              | otherwise   = y


(т.е. над произвольным _типом_ "a" можно ввести отношение частичного
порядка, если есть определение понятия эквивалентности над этим типом и
определена операция <= или compare -- все остальные будут выведены из этого
определения).

После этого я могу писать код, который работает с данными любого типа,
который может быть членом класса. Откуда взялся этот тип - мне не важно.
Если мне потребуется использовать чужую библиотеку/код, то все, что мне
надо будет сделать - указать, каким образом мои типы "укладываются" в
поведение, требуемое библиотечным кодом. Мне не надо наследовать,
переопределять, рефакторить ...

 AS> Также, если изменение семантики работы класса-родителя нарушает работу
 AS> программной системы, то, видимо, этот класс-родитель был плохо
 AS> написан. Хорошо написанный и продуманный класс-родитель не должен
 AS> допускать этого.
Как класс-родитель может что-то не допускать? От него пронаследуются,
перекроют его методы, а его и не спросят.

[skip]

>> Мне нужна композиция
>> функций. Есть знакомство с математическим смыслом этого понятия?
 AS> Нет. Я уже на это обращал внимание. :-( "Я ведь еще только учусь!.." 
 AS> (с) Шеф :-)
А где, если не секрет (можно в привате)?

 AS> Но мне бы интересно было бы узнать. Есть ли _доступная_ информация в
 AS> онлайне на русском (английском) языке? (В популярной форме, на уровень
 AS> выпускника средней школы)
Честно говоря, не скажу... Я с трудом представляю себе, что есть уровень
выпускника средней школы. Скажу только, что материала (разного уровня
сложности) в сети - хоть спину мой. Было бы желание и www.google.com.

 AS> Я влез в спор именно для того, чтобы понять, почему именно ФП тебе
 AS> показался лучше. Чем больше я спорил, тем больше я понимал, что это
 AS> далеко не зря, тем более что тебя стал кое в чем поддерживать V. 
 AS> Wagner и другие, которые у меня уже отмечены как местные "гуру" и
 AS> интеллигенция, к которым стоит прислушиваться. Когда мне упомянули про
 AS> функционалы и когда я не поленился посмотреть, что такое ФП на самом
 AS> деле, мне стало еще интереснее. Я понимал, что мои примеры, возможно,
 AS> реализуют не то, что требовалось, но хотел это вытянуть у тебя. Не
 AS> получилось.... :-)

 AS> Может все-таки объяснишь, что такое функционал в математике? Как можно
 AS> возвращать функцию функцией? Где можно и можно ли найти информацию в
 AS> онлайне?
Я бы не хотел заменять лектора курса по теории вычислений - это займет уйму
времени :) Информацию в сети наверняка найти можно, но сайт, с которого ты
скачаешь конспект лекций, врядли ответит на вопросы, которые у тебя будут
возникать :)


>> Мне нужен инструмент, который позволяет взять функцию string -> int
>> (например, с семантикой "кол-во строк в файле с указаным именем"), функцию
>> int -> string (например, с семантикой "число прописью"), сделать "compose
>> spell_number count_lines" и получить сущность типа "функция из string ->
>> string" с семантикой "число строк в файле с указаным именем прописью".
>> Напомню, что все это - иллюстрация того, что такое "first-class funcitons".

 AS> Знаешь, я бы тоже мог бы попросить инструмент на языке ФП, который бы
 AS> реализовывал сущность трехмерного персонажа мультфильма с речевыми и
 AS> двигательными функциями, который должен включать сущности трехмерного
 AS> объекта, речевого синтезатора и объекта получения команд движений
 AS> через манипуляторы.
Стоп. Отмотай тред.
-Что такого есть в ФП, чего нет, к примеру, в ООП, что характерно для ФП?
-Например, помимо всего прочего, first-class функции.
-Что это?
-Вот краткое описание, вот пример.
-Это ерунда, так можно и в С/C++, вот пример.
-Нет, эти примеры не равнозначны.
-Ну, знаешь ли, я тоже могу сейчас придумать такой пример, что ты будешь
год затылок чесать!

Улавливаешь, к чему я? Я не хочу забить собеседника в угол хитрой задачкой,
или что-то в этом роде. Я показываю, что существуют механизмы, которые
удобны на практике (комбинирование функций с целью получения новых с
задаными свойствами) и показываю, что, будучи естественными для одного
языка/стиля програмирования, они в то же время с трудом могут быть
реализованы (если могут быть реализованы вообще) средствами другого
языка/стиля. Не я первый начал говорить "такое есть и в C++".

 AS> Если еще раз вернуться к изначальному вопросу, а именно (цитирую):
 AS> "Просто были продемонстрированы некие принципы, которые, по моему
 AS> мнению, делают ФЯ более предпочтительными средствами для
 AS> программирования." - я могу сказать что-то подобное:

 AS> Как показывает практика, введение массовой компьютерной индустрии
 AS> вынуждает все большее количество людей заниматься программированием
 AS> (вплоть до написания JavaScript). Часто эти люди либо плохо знакомы,
 AS> либо вообще незнакомы с тонкостями современной математики, поэтому
 AS> чисто математические методы программирования для них неприемлемы, ибо
 AS> они слишком сложны для них.
Та математика, которая вспоминалась - ни разу не современная :)

 AS> На помощь им (и в пользу для общества в целом) приходит более простой
"1000000 леммингов не ошибаются" - это не аргумент. (см.
http://www.infidels.org/news/atheism/logic.html)

 AS> в понимании объектно-ориентированный метод программирования. Понимание
 AS> его главной концепции должно быть достаточно понятно - все, что нас
 AS> окружает, может быть представлено в виде классово-объектного понятия с
 AS> набором внутренных свойств и различного рода методов (в смысле -
 AS> функций/процедур).

А мне не понятно. Я утверждаю, что модель "все есть объект. свойства
объекта определяются его данными/поведением. любое действие есть поведение
какого-то объекта. для решения задачи надо построить правильную систему
объектов" проигрывает модели "известна цель и путь ее достижения. путь
выражается в операциях над сущностями. Операции требуют наличия у сущностей
определенного поведения. Любая сущность с правильным поведением может быть
субъектом операции. для решения задачи надо промоделировать исходные данных
в сущностях и описать операции". Мне кажется более очевидными подход "все. 
что имеет шестигранные выступы, можно с переменным успехом пытаться крутить
с помощью гаечного ключа", чем "все, что может крутится от применения
гаечного ключа, имеет в дальних родственниках гайку. В качестве побочного
эффекта, оно может крутиться само, и вовсе без ключа. Более того, если
хорошо поискать, обязательно где-то найдется резьба или ее рудименты, даже
если она не нужна"


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

 AS> Замечю только, что с языками ФП я не сталкивался лично, но,
 AS> познакомившись с его "каймой" из математических премудростей из 3
 AS> курса физмата (это идиома), вроде функционалов, я понял, что это не
 AS> для средних умов. То есть, не для широкого круга, а для специалистов.
Твое утверждение подразумевает то, что для использования ФЯ необходимо
знакомство с программой 3-го курса физмата. Это не так.

 AS> По-моему, достаточно наглядно выглядит интрукция объекту типа MickeyMouse:
 AS> Mickey.MoveUp();
Сходи в fido7.su.softw - там народ вторую неделю не может договориться, что
более интуитивно - печка.жарить(картошка), картошка.жариться(печка),
повар.жаритьВ(печка,картошка),рецепт.готовить(картошка.в(печка)), ...

 AS> Впрочем, не спорю, Mickey = MoveUp(Mickey) может также сойти, но это
 AS> уже немного более громоздко - два раза указан Mickey, что обычно людям
 AS> не нравится.
Это ты вообще к чему говоришь? :)

>> Елки же ж моталки. Ну что такого сложно в выражении "получить функцию z как
>> результат композиции функций f и g, где g - .... (см. выше)"? Неужели я
>> настолько путано все изложил?

 AS> Сложно для того, кто с таким никогда не сталкивался. Я кроме
 AS> императивных языков больше ничего-то и не видел поэтому понятия
 AS> композиции функций... В чем разница с простым возвращением значения
 AS> вторичной функции g(x) первичной функции f(g) в качестве аргумента? 
 AS> Нам не дан x? 8-
Да. Нам не дан х. Нам дано два поведения, мы хотим их скомбинировать.
Получить в результате новое поведение и потом его использовать где не
попадя. 


-- 
Dmitry Astapov //ADEpt                               E-mail: adept@umc.com.ua
GPG KeyID/fprint: F5D7639D/CA36 E6C4 815D 434D 0498  2B08 7867 4860 F5D7 639D



Reply to: