Форум: "Основная";
Текущий архив: 2014.01.19;
Скачать: [xml.tar.bz2];
Внизdelphi и обобщенное прораммирование Найти похожие ветки
← →
vlk32 (2011-11-01 15:20) [0]Хотел бы узнать мнение уважаемого сообщества по такой вот скользкой теме как обобщенное программирование. Сразу скажу, что я понимаю под этим два аспекта:
- структуры данных с обобщенным типом элементов (контейнеры)
- обобщенные алгоритмы над контейнерами
Недавно я предпринял попытку написать свою библиотеку, используя генерики (несмотря на то, что они пока "сырые"). Прошелся по нескольким граблям, посмотрел как сделаны коллекции по умолчанию в 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.
← →
vlk32 (2011-11-01 15:21) [1]<продолжение>
3.
Использование интерфейсов. Вся функциональность итератора/курсора разбивается на несколько интерфейсов по уровню функциональности (или какому то другому критерию). Все контейнеры которые мы хотим задействовать в обобщенных алгоритмах должны наследовать и реализовать интерфейсы курсора. В данном подходе никаких отдельных курсоров (в виде объектов или записей) не используется. В обобщенные алгоритмы передаются контейнеры и дополнительные параметры необходимые для выполнения конкретной операции (например индекс стартового элемента, индексы начала и конца диапазона и т.п.).
Так сделано в C#. Этот подход кажется мне не совсем удачным в таком плане:
- в обобщенные алгоритмы надо будет передавать индексы элементов вместо курсора. При этом для каких то типов контейнеров лучше чтобы это были именно целочисленные индексы, а для других указатели на элемент контейнера, т.о. на лицо нарушение общности.
- т.к. контейнер объявлен с обобщенным типом, то и обобщенная процедура также должна быть объявлена с обобщенным типом, т.е.
procedure ForEach<T>(C:TContainer<T>; Action:TActionOverSingleItem<T>); что не даст нам реализовать это через глобальную процедуру. Один из выходов сделать так:
Algo<T> = class
class procedure ForEach(C:TContainer<T>; Action:TActionOverSingleItem<T>);
end;
И вот такой дикий вызов мы в результате получаем Algo<TMyRec>.ForEach(MyList,DoClearMyRecord) Кстати этим же будет страдать любая попытка сделать обобщенный алгоритм средствами языка delphi.
4. Подход который я тестирую в настоящее время
Описываются курсоры двух типов:
TIterator<T> = class(TObject) - для одиночного значения
TRange<T>= class(TObject) - для диапазона значений (от А до Б, массив индексов и т.д.)
Все контейнеры наследуются от
TIterable<T> = class abstract
public
function Iterator(const S:TIteratorState):TIterator<T>; overload; virtual; abstract;
function Iterator(const I:Integer):TIterator<T>; overload; virtual; abstract;
function Iterator(const V:T):TIterator<T>; overload; virtual; abstract;
// function Range(const A,B:Integer):TRange<T>; overload; virtual; abstract;
// function Range(const Indexes:array of Integer):TRange<T>; overload; virtual; abstract;
end;
т.е. каждый контейнер должен уметь вернуть курсор (см. как в Generics.Collections возвращается енумератор). При этом в курсор внедряются все необходимые данные, методы, эффективно переопределяются существующие методы с учетом местной специфики и т.п. (естественно все это не будет доступно в обобщенном алгоритме, кроме определенного в TIterator) Само объявление курсора примерно такое
// uniform access to TContainer<T> via single item
TIterator<T> = class(TObject)
protected
FFlags : TIteratorFlags;
FInd : Integer;
FPtr : Pointer_To_<T>.Ptr;
function GetIsBack: Boolean;
function GetIsBidirectional: Boolean;
function GetIsFirst: Boolean;
function GetIsForward: Boolean;
function GetIsFront: Boolean;
function GetIsItem: Boolean;
function GetIsLast: Boolean;
function GetIsRandom: Boolean;
function GetItem: T;
procedure SetIndex(const Value: Integer);
procedure SetItem(const Value: T);
public
constructor Create;
// positioning
function Prev(const By:Integer=1):Integer; virtual;
function Next(const By:Integer=1):Integer; virtual;
// modify container storage
function Insert(const Item:T):Integer; virtual; abstract;
function Remove:Integer; virtual; abstract;
// compare with other iterator
function eq(const i:TIterator<T>):Boolean; virtual;
function ab(const i:TIterator<T>):Boolean; virtual;
function ls(const i:TIterator<T>):Boolean; virtual;
function le(const i:TIterator<T>):Boolean; virtual;
function ae(const i:TIterator<T>):Boolean; virtual;
function dist(const i:TIterator<T>):Integer; virtual;
// item access
property Val:T read GetItem write SetItem;
property Ptr:Pointer_To_<T>.Ptr read FPtr;
property Ind:Integer read FInd write SetIndex;
// state
property IsForward:Boolean read GetIsForward;
property IsBidirectional:Boolean read GetIsBidirectional;
property IsRandom:Boolean read GetIsRandom;
property Flags:TIteratorFlags read FFlags;
property IsFront:Boolean read GetIsFront;
property IsFirst:Boolean read GetIsFirst;
property IsLast:Boolean read GetIsLast;
property IsBack:Boolean read GetIsBack;
property IsItem:Boolean read GetIsItem;
end;
Тут есть проблемы с безопасностью курсора т.к. дается прямая ссылка на элемент в контейнере
property Ptr:Pointer_To_<T>.Ptr read FPtr;
С другой стороны это эффективный доступ к элементу и кроме того возможность использовать его как позицию (вместо индекса) для списков и деревьев.
Вторая плохая новость это то что итератор требует обобщенного типа, что не даст возможность объявить обобщенный алгоритм как
interface
procedure SomeAlgorithm(FromPos:TIterator; ToPos:TIterator; const Action:TActionFunc);
И вот наконец вопрос уважаемой общественности. Какой же все таки метод использовать с учетом текущих возможностей и ограничений языка delphi для реализации обобщенной библиотеки контейнеров и алгоритмов? Возможно есть какие то более продвинутые подходы чем те, которые описал я?
← →
Cobalt © (2011-11-02 13:25) [2]я полагаю, что лучше все-таки танцевать от задачи.
А дополнительные слои абстракции сами накрутятся на колеса процесса разработки.
← →
vlk32 (2011-11-02 14:18) [3]Меня такой подход уже не устраивает. Потому что ничего путного по ходу дела не накрутится. Библиотеки с костылями у меня есть. Теперь же хочется изначально все спланировать.
← →
jack128_ (2011-11-02 17:52) [4]
> Использование
> for_each (myvector.begin(), myvector.end(), myfunction);
>
>
> Обратите внимание - никакой информации об обобщенном типе
> в вызове for_each не используется. Возможно это тема для
> дискуссии, но мое мнение - реализовать подобную функциональность
> и синтаксис в таком же примерно виде в delphi даже с использованием
> генериков и анономных процедур не представляется возможным
Обычному разработчику невозможно. А вот разработчикам компилятора вполне возможно, что они сделали встроив аналог for_each(цикл for .. in .. ) в язык.
Другое дело, что другие алгоритмы по аналогу с std конечно не получится реализовать, хотя бы из за отсутствия в delphi утиной типизации.
> Контейнеры унаследованы от некоего базового класса (TIterator),
> который описывает максимально возможную функциональность
> итератора/курсора, т.е. возможность устанавливать курсор
> на требуемый элемент контейнера, перемещать курсор, читать/писать
> в элемент под курсором, вставлять/удалять элементы т.д.
> Каждый наследник переопределяет те функции курсора, которые
> он реально поддерживает, а на остальные вешает генерацию
> исключений.
ИМХО - отстойный вариант, потому что это по сути отход от статической типизации.
procedure MyFunc(Iter: TIterator<T>)
begin // на этапе компиляции - я не знаю, могу ли я добавлять/удалять элементы в контейнер.
end;
> Использование интерфейсов. Вся функциональность итератора/курсора
> разбивается на несколько интерфейсов по уровню функциональности
> (или какому то другому критерию). Все контейнеры которые
> мы хотим задействовать в обобщенных алгоритмах должны наследовать
> и реализовать интерфейсы курсора. В данном подходе никаких
> отдельных курсоров (в виде объектов или записей) не используется.
>
В C# не используется. Почему в дельфи нельзя использовать?
> Algo<T> = class
> class procedure ForEach(C:TContainer<T>; Action:TActionOverSingleItem<T>);
>
> end;
>
> И вот такой дикий вызов мы в результате получаем Algo<TMyRec>.
> ForEach(MyList,DoClearMyRecord) Кстати этим же будет страдать
> любая попытка сделать обобщенный алгоритм средствами языка
> delphi.
В теории можно написать так:
Algo2 = class
class procedure ForEach<T>(C:TContainer<T>; Action:TActionOverSingleItem<T>);
end
вызов
Algo2.ForEach(C, Action);
А любая попытка сделать обобщенный алгоритм в дельфи будет страдать прежде всего от глюков компилера.
← →
vlk32 (2011-11-02 19:59) [5]Algo2 = class
class procedure ForEach<T>(C:TContainer<T>; Action:TActionOverSingleItem<T>);
end
хрен редьки не слаще. есть два варианта и оба они к сожалению беспонтовые
algo<integer>.reverse(mylist);
algo.reverse<integer>(mylist);
Вот из за того что каждый раз надо будет прописывать тип и левое имя вместо
reverse(mylist);
уже честно говоря задумываюсь, а нафига оно надо? Но именно потому я и пишу в теме. Может я слепой и просто не вижу правильного логичного и красивого варианта реализации этой кухни в делфи?
← →
jack128_ (2011-11-02 22:19) [6]
> algo.reverse<integer>(mylist);
ты уверен, что нужно писать именно так??
такой код: algo.reverse(mylist);
не работает??
> уже честно говоря задумываюсь, а нафига оно надо? Но именно
> потому я и пишу в теме. Может я слепой и просто не вижу
> правильного логичного и красивого варианта реализации этой
> кухни в делфи?
меня от написания своей библиотеки алгоритмов для коллекций остановило прежде всего внезапные глюки компилятора в неожиданных местах. Это я говорю по итогам написания light ORM, которую у нас в конторе написали. Наверно процентов 40% времени было потрачено на переделку кода, что бы он банально скомпилировался, банально всякие интернал ерроры запарили.
Ну и конечно неудобоваримый синтаксис лямбд тоже внес свою лепту.
← →
vlk32 (2011-11-02 22:41) [7]интернал ероры решаются периодическим жмаком по клавишам Shift+F9
Правда иногда возникает такая комбинация кода когда уже и это не помогает и тогда надо смотреть что ты написал в крайние пять минут и отказаться от этого. я пока до таких ситуаций не доходил. но это чисто техническая проблема и я надеюсь эмбаркодеро её пофиксит.
let.foreach(a);
[DCC Error] test.pas(169): E2010 Incompatible types: "TBaseArray<test.let.foreach.T>" and "TBaseArray<System.Real>"
let.foreach<real>(a);
OKAY
так что я уверен что иначе - никак. компилятор требует точно указать тип в конце концов. по другому он пока не может.
← →
_Юрий (2011-11-03 17:07) [8]
> vlk32 (02.11.11 22:41) [7]
>
> Правда иногда возникает такая комбинация кода когда уже
> и это не помогает и тогда надо смотреть что ты написал в
> крайние пять минут и отказаться от этого. я пока до таких
> ситуаций не доходил. но это чисто техническая проблема и
> я надеюсь эмбаркодеро её пофиксит.
особенно весело бывает, когда проводишь рефакторинг, и проект пару дней вообще не собирается, а потом наконец то должен собраться - и нате вам, internal error, отменяйте последние изменения. На "пофиксит" надежда слабая.
> jack128_ (02.11.11 22:19) [6]
> меня от написания своей библиотеки алгоритмов для коллекций
> остановило прежде всего внезапные глюки компилятора в неожиданных
> местах. Это я говорю по итогам написания light ORM, которую
> у нас в конторе написали. Наверно процентов 40% времени
> было потрачено на переделку кода, что бы он банально скомпилировался,
> банально всякие интернал ерроры запарили.
Во, у нас тоже самое случилось.
И лямбды эмберкадеро сделали ужасно. И мусор не собирается. И yield return нету. В общем, не взлетит.
Берем Generics.Collections и наслаждаемся тем, что имеем )
← →
_Юрий (2011-11-03 17:18) [9]
> я пока до таких
> ситуаций не доходил
C ORM как раз дошли. Причем сам по себе фреймворк компилировался. Не компилировалась попытка его использования уже в приложении. Причем интерфейс фреймворка был пока без реализации, а других вариантов интерфейса особо и не было, учитывая громоздкость языка.
Вот и думай тут, какие изменения отменять. В результате пляски с бубном, попытки скомплироваться за счет ухудшения качества интерфейса, испорченное настроение, демотивация. Как то так
← →
jack128_ (2011-11-04 00:14) [10]
> _Юрий (03.11.11 17:18) [9]
кстати, а какой интерфейс для выборок у вас получился?? Мы в результате что то типа такого получили
for Obj in Storage.Select<TMyObject>("select ... ", True) do
begin
end; // если второй параметр - True, объект Obj сам уничтожится в конце итерации, если False - сами должны уничтожать.
Ну набор вспомогательных хелперов типа:MyObjectList := Storage.SelectList<TMyObject>("select ...");
MyObj := Storage.SelectObject<TMyObject>("select ...");
for Obj in Storage.SelectAll<TMyObject>(True) do // имя таблицы берется из атрибута навешанного на класс TMyObject
и т д..
← →
vlk32 (2011-11-04 00:22) [11]Удалено модератором
← →
vlk32 (2011-11-04 00:46) [12]О ла ла. А а в XE2 все нормально. Это радует.
← →
_Юрий (2011-11-04 10:15) [13]
> jack128_ (04.11.11 00:14) [10]
> кстати, а какой интерфейс для выборок у вас получился??
В общем то, такой же. Только там вместо "select... " RTTI-шный построитель запроса на аттрибутах.
> vlk32 (04.11.11 00:46) [12]
> О ла ла. А а в XE2 все нормально. Это радует.
>
>
Вот так. Интересно. Мы на 2010 тренировались.
> class procedure let.foreach<T>(const C:TIterable<T>; const
> Action:TActionProc1<T>);
Я не понял, почему вы делаете это через внешний статический класс, почему не сделать ForEach у самого контейнера?
И кстати, учитывая громоздкость лямбд, лучше на мой взгляд делать обычный цикл через стандартный энумератор.
Сравните:
for X in Container do
Summa := Summa + X.Value;
или же
Container.ForEach(procedure(X: TMyObject)
begin
Summa := Summa + X.Value;
end);
второй вариант более громоздкий, следовательно он хуже.
А вот реализация энумератора должна быть своя для каждого типа контейнера, и пользователь должен быть абстрагирован от этой реализации.
В варианте цикла for in это как раз и достигается.
← →
vlk32 (2011-11-04 11:27) [14]Ну как бы я исходил из того что тот же ForEach - это ведь просто перебор элементов контейнера (заметьте, не обязательно всех от первого до последнего) и выполнение некой операции с этими элементами. Т.е. такую штуку можно сделать с любым контейнером. Т.е. это обобщенный алгоритм. Я его написал один раз и для любого контейнера который поддерживает итераторы его можно применить. И таких алгоритмов - около сотни. Вы на минутку можете себе представить контейнер который все это будет реализовать внутри себя? А если контейнеров десять штук?
Идем далее. Я например могу написать вот так
var a,b : TBaseArray<TMyRec>;
.. init a ..
b := let.extract<TMyRec>(a.range(MySelector));
let.foreach<TMyRec>(b.range(0,8),MyPerfectAction);
Вот и функция экстракт стала обобщенным алгоритмом - и ее теперь не надо тулить во все контейнеры. А ForEach уже умеет перебирать в заданном диапазоне. А диапазон может быть указан вот так
var indexes : Array_Of_<integer>;
N : Integer;
b.range(iAll)
b.range(7,N)
b.range(Indexes)
b.range(function(const Item:TMyRec; const I:Integer):Boolean
begin
Result := (Item.MyField > N) and (Item.MyField2 in [colBlack,colRed,colGreen]);
end;)
Удобно, правда?
Конечно, надо понимать, что вся эта универсальность и гибкость в ущерб производительности.
← →
_Юрий (2011-11-04 14:29) [15]
> vlk32 (04.11.11 11:27) [14]
> Ну как бы я исходил из того что тот же ForEach - это ведь
> просто перебор элементов контейнера (заметьте, не обязательно
> всех от первого до последнего) и выполнение некой операции
> с этими элементами. Т.е. такую штуку можно сделать с любым
> контейнером. Т.е. это обобщенный алгоритм. Я его написал
> один раз и для любого контейнера который поддерживает итераторы
> его можно применить.
ну и в случае через GetEnumeator тоже самое - не обязательно все элементы возвращаются, зависит от реализации.
Объявить интерфейс IEnumerable<T> c методом GetEnumerator. В D2010 такой ход не прошел по причине ошибок компилятора
Если бы язык позволял делать хелперы к интерфейсам, то можно было бы раскрутиться отсюда, было бы все удобно. Но поддержки хелперов вроде не предвидится.
по поводу let.foreach<TMyRec>(b.range(0,8),MyPerfectAction);
Тут возникает единственный вопрос - кто управляет временем жизни этого самого результата от b.range? Это ведь некий контейнер, результат фильтрации изначального, очевидно, уже другой инстанс.
В этих ваших сишарпах все просто - там сборщик мусора. А нам тут как быть?
← →
Anatoly Podgoretsky © (2011-11-04 14:48) [16]А вам бежать на C# или не делать странного.
← →
vlk32 (2011-11-04 14:58) [17]b.range(0,8) - да тут объект b создает новый объект типа TRange - обобщенное описание некоего подмножества элементов из b. Конечно сами элементы в этом новом объекте не хранятся. Т.к. этот объект нужен исключительно в обобщенном алгоритме то логично будет его в этом же алгоритме и освободить в конце когда он уже не нужен.
← →
jack128_ (2011-11-04 23:03) [18]Скажу как бы я делал, если бы не глюки компилятора, то есть в будущих(выше 2010) версиях такое( может быть) и прокатит.
Собственно - копия LINQ. Без хелперов к интерфейсам и дженерик классам - это выглядело бы примерно так:ILinqEnumerable<T> = interface(IEnumerable<T>)
function Select<T, TResult>(Selector: TFunc<T, TResut>): ILinqEnumerable<TResult>;
function Where<T, T>(Predicte: TFunc<T, boolean>): ILinqEnumerable<T>;
.... // и так далее
end;
TLinqEnumerableImpl<T> = class
contructor Create(AEnum: IEnumerable<T>);
function Select<T, TResult>(Selector: TFunc<T, TResut>): ILinqEnumerable<TResult>;
function Where<T, T>(Predicte: TFunc<T, boolean>): ILinqEnumerable<T>;
// и так далее
end;
TMyList<T> = class(TInterfacedObject, IEnumerable<T>,
ILinqEnumerable<T>)
private LinqEnum: ILinqEnumerable<T> read FLinqEnum implements ILinqEnumerable<T>; // FLinqEnum := TLinqEnumerableImpl<T>.Create(Self);
public
property Items[Index: Integer]: T read; write;
end;
В зависимости от реализации TLinqEnumerableImpl генерация последовательностей может быть ленивая или нет.
ну и пример использования:MyList
.Where(MyFilterFunc)
.GroupBy(MyGroupProc)
.Select(MySelectProc);
но естественно - все это не взлетит. Синтаксис лямбд убог и с этин котжировцы ничего не хотят делать..
← →
_Юрий (2011-11-05 09:33) [19]
> А вам бежать на 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]
> Синтаксис лямбд убог и с этин котжировцы ничего не хотят
> делать..
А точно ли ничего не хотят делать? может, они просто не в курсе?
← →
vlk32 (2011-11-05 11:00) [20]
> Кто запоминает ссылку, кто ее потом освобождает, и в какой
> момент?
> Согласитесь, этим должен заниматься движок, а не пользовательский
> код.
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)
Куда еще дальше терять в производительности?
← →
vlk32 (2011-11-05 11:02) [21]Уточняю. В примере выше Range мог создаваться либо прямо в вызове либо предварительно (ссылка на него есть). Можно принять соглашение что все итераторы поступившие в функцию обобщенного алгоритма будут гарантировано в нем освобождены. Ну как один из вариантов решения этой проблемы.
← →
jack128_ (2011-11-05 11:25) [22]
> А точно ли ничего не хотят делать? может, они просто не
> в курсе?
в курсе, я на конференции в Москве спрашивал об этом. Мне посоветовали Live Template (или там эта хрень называется) написать.
← →
oxffff © (2011-11-05 11:32) [23]
> Собственно - копия LINQ
:)
Для этого нужно как минимум ввести в Delphi понятие анонимного типа.
И поработать над выводом типов в Delphi.
← →
jack128_ (2011-11-05 14:24) [24]
> Для этого нужно как минимум ввести в Delphi понятие анонимного
> типа.
Для LINQ to Objects - это совсем не критично. ИМХО, анонимные типы для LINQ To SQL прежде всего сделаны.
> И поработать над выводом типов в Delphi.
Ну конечно было бы не плохо, но главное - синтаксис лямбд.
← →
Kerk © (2011-11-06 01:46) [25]
> jack128_ (02.11.11 17:52) [4]
>
> Другое дело, что другие алгоритмы по аналогу с std конечно
> не получится реализовать, хотя бы из за отсутствия в delphi
> утиной типизации.
В Delphi Prism оно есть. Может, когда-нибудь и в обычном сделают.
← →
Kerk © (2011-11-06 01:48) [26]Хм...
http://tzblok.blogspot.com/2011/08/duck-typing-v-delphi.html
← →
Kerk © (2011-11-06 01:57) [27]Вообще, я очень давно таких содержательных обсуждений не видел :)
Интересно.
← →
vlk32 (2011-11-06 02:27) [28]Посмотрел пример про утку и персону. Оригинально. Но вряд ли применимо на практике. Например я добавил в метод кваканья несколько параметров. А вот здесь оставил все как было:
procedure InTheForest(V: Variant);
begin
V.Quack; <<< теперь в этом методе есть параметры но здесь они не указаны
V.Feathers;
end;
Вопрос. Что будет если дело дойдет до Quack и он начнет обрабатывать параметры которые ему даже не передали? Но при этом все компилируется. Даже варнингов нет. Короче идеальная система для того чтобы прострелить себе обе ноги, и в конце концов голову.
← →
jack128_ (2011-11-06 02:35) [29]
> Хм...
> http://tzblok.blogspot.com/2011/08/duck-typing-v-delphi.
> html
Это еще времен дельфи 7(а то и раньше, не в курсе) техника. Естественно нужна диспетчеризация в компил тайм.
← →
jack128_ (2011-11-06 02:36) [30]
> Естественно нужна диспетчеризация в компил тайм.
Ну то есть что значит нужна? Естественно всегда можно все проверки в рантайм вынести. Только нафиг тогда дельфи, если есть javaScript ?
← →
_Юрий (2011-11-06 11:33) [31]
> vlk32 (05.11.11 11:02) [21]
> Можно принять соглашение что все итераторы поступившие
> в функцию обобщенного алгоритма будут гарантировано в нем
> освобождены.
А что делать, если пользователь передал туда итератор, который ему еще нужен?
← →
vlk32 (2011-11-06 11:51) [32]
> А что делать, если пользователь передал туда итератор, который
> ему еще нужен?
Остается последний вариант. По умолчанию итератор создается со специальным флагом освобождения в самом алгоритме. Если нужно оставить итератор живым (считаем что это не такой частый случай, то меняем флаг)
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 13:35) [33]
> vlk32 (06.11.11 11:51) [32]
>
Флаги вносят некоторую корявость, и простор к запутыванию, особенно при поведении по умолчанию "разрушу параметр". Это нестандартное поведение, которое непривычно для пользователя.
А вот в случае использования интерфейсов, реализованных через саморазрушающиеся объекты, эти проблемы отсутствуют в принципе.
При этом пользователь будет освобожден от обязанности убирать за собой мусор.
Что касается снижения производительности - то это натурально копейки, операция поиска адреса вызова чрезвычайно быстрая.
← →
vlk32 (2011-11-06 15:26) [34]В принципе согласен. С удовольствием попробовал бы сделать либу на интерфейсах, но пока отложу эту идею в сторону.
← →
jack128_ (2011-11-06 21:24) [35]Внезапно такой вот код нарылся:
http://code.google.com/p/delphi-spring-framework/source/browse/trunk/Source/System/Collections/Spring.Collections.pas
← →
vlk32 (2011-11-06 22:05) [36]Да про эту либу слышал краем уха. Порт (повторение?) джавовской либы в делфях. Вот только ее версия сильно смущает. Оно вообще доведено до логического конца?
← →
jack128_ (2011-11-06 22:15) [37]Ну я не джавист, но судя по тому, что в Collections реализован именно LINQ, то вряд ли это чистый порт джавы.
Ну и насколько это production ready либа - тоже сказать не могу, только пару часов назад как нарыл её..
Я так, чисто в качестве примера линк дал...
← →
oxffff © (2011-11-08 11:10) [38]Еще одна реализация duck typing
http://www.delphifeeds.com/go/s/86051
Страницы: 1 вся ветка
Форум: "Основная";
Текущий архив: 2014.01.19;
Скачать: [xml.tar.bz2];
Память: 0.61 MB
Время: 0.004 c