![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
delphi и обобщенное прораммирование
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]
> Синтаксис лямбд убог и с этин котжировцы ничего не хотят
> делать..
А точно ли ничего не хотят делать? может, они просто не в курсе?