Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

delphi и обобщенное прораммирование


Страницы:   1 2

vlk32 ©   (2011-11-01 15:20:00) 

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

Недавно я предпринял попытку написать свою библиотеку, используя генерики (несмотря на то, что они пока "сырые"). Прошелся по нескольким граблям, посмотрел как сделаны коллекции по умолчанию в Delphi, как тоже самое устроено в STL и C#. И пришел к таким выводам (поправьте меня, если в чем то неправ), о том как можно все устроить.

0. Эталонный подход STL
[container] <--> [iterator] <--> [generic algorithm]

Пример
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
 {
   for ( ; first!=last; ++first ) f(*first);
   return f;
 }

Использование
for_each (myvector.begin(), myvector.end(), myfunction);

Обратите внимание - никакой информации об обобщенном типе в вызове for_each не используется. Возможно это тема для дискуссии, но мое мнение - реализовать подобную функциональность и синтаксис  в таком же примерно виде в delphi даже с использованием генериков и анономных процедур не представляется возможным

Остаются следующие варианты

1.
Контейнеры выполнены индивидуально.
Каждый контейнер максимально эффективно реализует собственную функциональность с учетом особенностей внутренней структуры хранения элементов.
Семантически идентичные функции вполне могутиметь различный синтаксис. Например, добавление элемента в конец контейнера:
для массива procedure Add(item:T);
для списка  procedure AddNode(node:TListNode);
для стека procedure Push(const Value: T); и т.д. и т.п.
Так сделано в Generics.Collections где все контейнеры унаследованы от TEnumerable<T> - беспонтового класса необходимого только для того чтобы с этими контейнерами писать for item in <container> do begin ..do some with item end;
Естественно, что ни о каких обобщенных алгоритмах речи не идет. Т.е. вся экстра-функциональность впихивается либо в базовый контейнер либо в его потомков. И так для каждого контейнера по отдельности.

2.
Контейнеры унаследованы от некоего базового класса (TIterator), который описывает максимально возможную функциональность итератора/курсора, т.е. возможность устанавливать курсор на требуемый элемент контейнера, перемещать курсор, читать/писать в элемент под курсором, вставлять/удалять элементы  т.д. Каждый наследник переопределяет те функции курсора, которые он реально поддерживает, а на остальные вешает генерацию исключений. Кроме того наследник определяет несколько функций которые возвращают готовые курсоры на начало, конец, произвольную позицию в контейнере. Сам курсор может быть записью такого типа

Cursor = record
 flags : <состояние курсора>;
 Owner : TIterator; <<< ссылка на владельца курсора
 case <тип контейнера> of
  Array: ( position : Integer);
  List: (dnode : TListNode);
  < и т.д. >  
end;

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


oxffff   (2011-11-08 11:10:00) [1]

Еще одна реализация duck typing

http://www.delphifeeds.com/go/s/86051


jack128_   (2011-11-06 22:15:00) [2]

Ну я не джавист, но судя по тому, что в Collections реализован именно LINQ, то вряд ли это чистый порт джавы.
Ну и насколько это production ready либа - тоже сказать не могу, только пару часов назад как нарыл её..
Я так, чисто в качестве примера линк дал...


vlk32   (2011-11-06 22:05:00) [3]

Да про эту либу слышал краем уха. Порт (повторение?) джавовской либы в делфях. Вот только ее версия сильно смущает. Оно вообще доведено до логического конца?


jack128_   (2011-11-06 21:24:00) [4]

Внезапно такой вот код нарылся:

http://code.google.com/p/delphi-spring-framework/source/browse/trunk/Source/System/Collections/Spring.Collections.pas


vlk32   (2011-11-06 15:26:00) [5]

В принципе согласен. С удовольствием попробовал бы сделать либу на интерфейсах, но пока отложу эту идею в сторону.


_Юрий   (2011-11-06 13:35:00) [6]


> vlk32   (06.11.11 11:51) [32]
>


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


vlk32   (2011-11-06 11:51:00) [7]


> А что делать, если пользователь передал туда итератор, который
> ему еще нужен?


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

var i : TRange<MyRec>;
    j : Array_Of_<Integer>;
 
let.extract(a.range(0,a.Count div 2)); <<< range будет уничтожен в let.extract

i := a.range(iAll,False); <<< создали итератор со сброшенным флагом авто-уничтожения
i.setup(j);
let.extract(i);
i.setup(iAll);<<< итератор все еще доступен
i.Free; <<< мы должны освободить его сами


_Юрий   (2011-11-06 11:33:00) [8]


> vlk32   (05.11.11 11:02) [21]


>  Можно принять соглашение что все итераторы поступившие
> в функцию обобщенного алгоритма будут гарантировано в нем
> освобождены.

А что делать, если пользователь передал туда итератор, который ему еще нужен?


jack128_   (2011-11-06 02:36:00) [9]


> Естественно нужнадиспетчеризация в компил тайм.

Ну то есть что значит нужна? Естественно всегда можно все проверки в рантайм вынести. Только нафиг тогда дельфи, если есть  javaScript ?


jack128_   (2011-11-06 02:35:00) [10]


> Хм...
> http://tzblok.blogspot.com/2011/08/duck-typing-v-delphi.
> html

Это еще времен дельфи 7(а то и раньше, не в курсе) техника.  Естественно нужна диспетчеризация в компил тайм.


vlk32   (2011-11-06 02:27:00) [11]

Посмотрел пример про утку и персону. Оригинально. Но вряд ли применимо на практике. Например я добавил в метод кваканья несколько параметров. А вот здесь оставил все как было:


procedure InTheForest(V: Variant);
begin
V.Quack; <<< теперь в этом методе есть параметры но здесь они не указаны
V.Feathers;
end;


Вопрос. Что будет если дело дойдет до Quack и он начнет обрабатывать параметры которые ему даже не передали? Но при этом все компилируется. Даже варнингов нет. Короче идеальная система для того чтобы прострелить себе обе ноги, и в конце концов голову.


Kerk   (2011-11-06 01:57:00) [12]

Вообще, я очень давно таких содержательных обсуждений не видел :)
Интересно.


Kerk   (2011-11-06 01:48:00) [13]

Хм...
http://tzblok.blogspot.com/2011/08/duck-typing-v-delphi.html


Kerk   (2011-11-06 01:46:00) [14]


> jack128_   (02.11.11 17:52) [4]
>
> Другое дело, что другие алгоритмы по аналогу с std конечно
> не получится реализовать, хотя бы из за отсутствия в delphi
> утиной типизации.

В Delphi Prism оно есть. Может, когда-нибудь и в обычном сделают.


jack128_   (2011-11-05 14:24:00) [15]


> Для этого нужно как минимум ввести в Delphi понятие анонимного
> типа.

Для LINQ to Objects - это совсем не критично. ИМХО, анонимные типы для LINQ To SQL прежде всего сделаны.


> И поработать над выводом типов в Delphi.

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


oxffff   (2011-11-05 11:32:00) [16]


> Собственно - копия LINQ


:)

Для этого нужно как минимум ввести в Delphi понятие анонимного типа.
И поработать над выводом типов в Delphi.


jack128_   (2011-11-05 11:25:00) [17]


> А точно ли ничего не хотят делать? может, они просто не
> в курсе?

в курсе, я на конференции в Москве спрашивал об этом. Мне посоветовали Live Template (или там эта хрень называется) написать.


vlk32   (2011-11-05 11:02:00) [18]

Уточняю. В примере выше Range мог создаваться либо прямо в вызове либо предварительно (ссылка на него есть). Можно принять соглашение что все итераторы поступившие в функцию обобщенного алгоритма будут гарантировано в нем освобождены. Ну как один из вариантов решения этой проблемы.


vlk32   (2011-11-05 11:00:00) [19]


> Кто запоминает ссылку, кто ее потом освобождает, и в какой
> момент?
> Согласитесь, этим должен заниматься движок, а не пользовательский
> код.



function let.extract<T>(const R:TRange<T>):TIterable<T>;
begin
if (R=Nil) or R.IsEmpty then Exit(Nil);
Result := R.Owner.Create(R);
R.Remove;
R.Free;
^^^^ R было создано прямо в вызове let.extract, у вызывающего нет на него ссылки поэтому надо освободить прямо здесь.
end;


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

От интерфейсов сознательно отказался, потому что

- чистый цикл, время = 1
- foreach реализованный в контейнере (без итераторов), время = 2
- foreach через итераторы (никаких интерфейсов!), время от 5 до 7.5 (а может и до 10)

Куда еще дальше терять в производительности?


_Юрий   (2011-11-05 09:33:00) [20]


> А вам бежать на C# или не делать странного.


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


> vlk32   (04.11.11 14:58) [17]



> логично будет его в этом же алгоритме и освободить в конце
> когда он уже не нужен.


Как это будет выглядеть?
Вот, например, по [14]
var a,b : TBaseArray<TMyRec>;

.. init a ..

b := let.extract<TMyRec>(a.range(MySelector));
let.foreach<TMyRec>(b.range(0,8),MyPerfectAction);


Кто запоминает ссылку, кто ее потом освобождает, и в какой момент?
Согласитесь, этим должен заниматься движок, а не пользовательский код.


> jack128_   (04.11.11 23:03) [18]



>  Синтаксис лямбд убог и с этин котжировцы ничего не хотят
> делать..


А точно ли ничего не хотят делать? может, они просто не в курсе?


Страницы:   1 2

Наверх

Титульная страница Поиск, карта сайта Написать письмо