Локальные лямбды. Proof of concept

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Локальные лямбды. Proof of concept

Сообщение MageSlayer » 25.02.2010 11:03:39

Привет всем

Давно меня уже мучает отсутствие лямбд во FPC.
Казалось бы все есть - и тип вызова of object и локальные функции. Вроде бы до цели один шаг. По крайней мере в простейшей реализации.

Я попробовал слепить так сказать Proof of concept, для таких себе простейших и довольно быстрых операций. Хотел бы услышать ваши комментарии. Насколько я понимаю, такая же идея трамплинов используется в gcc.

Было бы отлично, если бы Сергей Горелкин и кто-нибудь из разработчиков/хакеров компилятора прокомментировал этот код.
Код: Выделить всё
program project1;

{$mode objfpc}{$H+}

{$OPTIMIZATION OFF} //с включенной оптимизацией регистр ebp может не хранить кадр стека.
{$ASMMODE INTEL}

type
  TTrampolineRec = record
    Reg_EBP:Pointer;
    LocalProc:Pointer;
    Self:Pointer; //будет использоваться в случае локальных функций методов классов
  end;
  PTrampolineRec = ^TTrampolineRec;

  TEvent = procedure (i:integer) of object;

procedure Test(E:TEvent);
var i:integer;
begin
  for i:=0 to 9 do //десять раз вызовем локальную функцию через трамплин.
    E(i);             //заодно и балансировку стека проверим.
end;

procedure Trampoline; //получает неявно self, в нем хранится PTrampolineRec
begin
  asm
    mov ebx, TTrampolineRec(eax).LocalProc
    mov eax, TTrampolineRec(eax).Reg_EBP
    call ebx
  end;
end;

function TrampRef(T:PTrampolineRec; P:Pointer):TEvent;
begin
  T^.LocalProc:=P;
  TMethod(Result).Code:=@Trampoline;
  TMethod(Result).Data:=T;
end;

procedure T1(T1_i:integer);
var T1_n:Integer;
    T1_Event:TEvent;

    T1_Tramp:TTrampolineRec; //служебная запись трамплина

procedure T2(i:integer);
begin
  WriteLn('Итерация - ', i);
  WriteLn(T1_i);
  WriteLn(T1_n);
end;

begin
  T1_n:=10;

  //подготовка трамплина
  asm
    mov T1_Tramp.Reg_EBP, ebp;
  end;
  T1_Event:=TrampRef(@T1_Tramp, @T2);

  Test(T1_Event);
  //в идеале хотелось бы видеть, просто Test(@T2), а подготовкой трамплина занимался бы компилятор

  T2(0);
end;

{$R project1.res}

begin
  T1(20);
end.



Как будто бы ничего военного, плюс все существующие классы/callback'и/event'ы (с объявлением of object) должны заработать практически бесплатно.
Даже отдельный тип не надо вводить.
Может чего-то я упускаю?
Последний раз редактировалось MageSlayer 25.02.2010 11:56:15, всего редактировалось 1 раз.
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Локальные лямбды. Proof of concept

Сообщение Max Rusov » 25.02.2010 11:35:06

MageSlayer писал(а):Давно меня уже мучает отсутствие лямбд во FPC.

Че за хрень?
Max Rusov
постоялец
 
Сообщения: 191
Зарегистрирован: 25.04.2009 15:46:03

Re: Локальные лямбды. Proof of concept

Сообщение MageSlayer » 25.02.2010 11:46:11

Max Rusov писал(а):
MageSlayer писал(а):Давно меня уже мучает отсутствие лямбд во FPC.

Че за хрень?


Тип reference в Delphi 2009 (http://8vmr.livejournal.com/6114.html). Они же closures/замыкания.
Мой пример поизучайте.
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Локальные лямбды. Proof of concept

Сообщение Max Rusov » 25.02.2010 12:26:12

Из вашего примера я понял только одно, что Вы хотите передать куда-то указатель на локальную функцию, чтобы потом ее вызвать. Если это и есть "лямбды", то они уже есть :). Я, например, это использую постоянно. Немного в другом варианте, чуть более красивом, IMHO:

Код: Выделить всё
{$mode Delphi}
{$AsmMode Intel}

program Test1;

  function LocalAddr(Proc :Pointer) :TMethod; assembler;
  asm
    mov [edx + 4], EBP
    mov [edx].DWORD, Proc
  end;

  type
    TLocalProc = procedure(A :Integer) of object;

  procedure SomeProc(ACount :Integer; const AProc :TMethod);
  var
    I :Integer;
  begin
    for I := 0 to ACount - 1 do
      TLocalProc(AProc)(I);
  end;

  procedure Test;
  var
    L :Integer;

    procedure LocalFunc(A :Integer);
    begin
      Writeln('A=', A, ' L=', L);
    end;

  begin
    L := 666;
    SomeProc(10, LocalAddr(@LocalFunc));
  end;

begin
  Test;
end.


С небольшими вариациями работает в Delphi. А вообще локальные enumeratior-ы использовались еще в Turbo Pascal, правда без красивого слова "лямбды".
Max Rusov
постоялец
 
Сообщения: 191
Зарегистрирован: 25.04.2009 15:46:03

Re: Локальные лямбды. Proof of concept

Сообщение MageSlayer » 25.02.2010 13:13:58

Max Rusov писал(а):Из вашего примера я понял только одно, что Вы хотите передать куда-то указатель на локальную функцию, чтобы потом ее вызвать. Если это и есть "лямбды", то они уже есть :). Я, например, это использую постоянно.


Именно. И именно их поддержки в языке нет.
Компилятор должен проверять сигнатуру функции самостоятельно, без приведения типов. Иначе это обычный хак, чуть более красивый или чуть менее.

Max Rusov писал(а):А вообще локальные enumeratior-ы использовались еще в Turbo Pascal, правда без красивого слова "лямбды".


Красивое слово "лямбда" появилось где-то лет за 40 до проектирования Паскаля, тем более Турбо.

Было бы очень неплохо иметь поддержку в компиляторе.
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Локальные лямбды. Proof of concept

Сообщение Sergei I. Gorelkin » 25.02.2010 17:28:38

1) Вложенная ф-ция и метод объекта - совершенно разные вещи, несмотря на то, что технически у них обоих есть скрытый первый параметр (у вложенной ф-ции он называется parentfp, а не self). Стремление сделать их совместимыми идет, так сказать, вразрез с самой сущностью Паскаля :)
С этой точки зрения в Дельфи новый тип и анонимные классы сделаны совсем не зря, хотя реализация получается гораздо более тяжеловесная.

2) Основная проблема в том, что указатель на вложенную ф-цию действителен только во время выполнения внешней ф-ции. Т.е. если процедура Test вздумает сохранить свой аргумент E где-нибудь в Button1.OnClick, то в дальшейшем при клике по этой кнопке все рухнет нафиг.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1395
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Локальные лямбды. Proof of concept

Сообщение Max Rusov » 25.02.2010 18:04:23

Sergei I. Gorelkin писал(а):1) Вложенная ф-ция и метод объекта - совершенно разные вещи, несмотря на то, что технически у них обоих есть скрытый первый параметр (у вложенной ф-ции он называется parentfp, а не self). Стремление сделать их совместимыми идет, так сказать, вразрез с самой сущностью Паскаля :)

Они совместимы лишь по формату вызова. Не вижу никакой трагедии. Вот в Delphi они несовместимы - и это очень неудобно, приходится вызывать локальную процедуру (лямбду :)) через ассемблерную вставку. В FPC лучше.

Sergei I. Gorelkin писал(а):2) Основная проблема в том, что указатель на вложенную ф-цию действителен только во время выполнения внешней ф-ции. Т.е. если процедура Test вздумает сохранить свой аргумент E где-нибудь в Button1.OnClick, то в дальшейшем при клике по этой кнопке все рухнет нафиг.

Не вижу проблемы. Просто не надо так делать. Указатель на метод объекта тоже валиден только до тех пор, пока не уничтожен объект. Никто же не считает это проблемой.
Max Rusov
постоялец
 
Сообщения: 191
Зарегистрирован: 25.04.2009 15:46:03

Re: Локальные лямбды. Proof of concept

Сообщение Sergei I. Gorelkin » 25.02.2010 18:59:29

...кстати, исходная задача решается на порядок проще и без всякого ассемблера:
Код: Выделить всё
{$mode objfpc}{$H+}

{$OPTIMIZATION OFF} //с включенной оптимизацией регистр ebp может не хранить кадр стека.

type
  TEvent = procedure (i:integer) of object;

procedure Test(E:TEvent);
var i:integer;
begin
  for i:=0 to 9 do //десять раз вызовем локальную функцию через трамплин.
    E(i);             //заодно и балансировку стека проверим.
end;

procedure T1(T1_i:integer);
var
  m: TMethod;
T1_n:Integer;

procedure T2(i:integer);
begin
  WriteLn('Итерация - ', i);
  WriteLn(T1_i);
  WriteLn(T1_n);
end;

begin
  T1_n:=10;
  m.code := @T2;
  m.Data := get_frame;
 
  Test(TEvent(m));
  T2(0);
end;

begin
  T1(20);
end.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1395
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Локальные лямбды. Proof of concept

Сообщение MageSlayer » 25.02.2010 21:33:36

Sergei I. Gorelkin писал(а):1) Вложенная ф-ция и метод объекта - совершенно разные вещи, несмотря на то, что технически у них обоих есть скрытый первый параметр (у вложенной ф-ции он называется parentfp, а не self). Стремление сделать их совместимыми идет, так сказать, вразрез с самой сущностью Паскаля :)


Если можно по-подробнее. Собственно по поводу концептуальной разницы, вопросов нет. Сымитировать ООП можно и просто на базе лямбд.
Я бы назвал это просто оптимизацией, т.к. вместо кучи размещаем данные лямбды в стеке.
Может даже создать дополнительный процедурный тип. Типа TLocalClosure. И обеспечить прозрачное приведение типов между TMethod, TLocalClosure и reference to (или как он там официально в D2009 называется?).

Sergei I. Gorelkin писал(а):С этой точки зрения в Дельфи новый тип и анонимные классы сделаны совсем не зря, хотя реализация получается гораздо более тяжеловесная.


Да, создавать экземпляр класса с счетчиком ссылок в куче, тут сложно придумать что-то еще более тяжеловесное :)

Sergei I. Gorelkin писал(а):2) Основная проблема в том, что указатель на вложенную ф-цию действителен только во время выполнения внешней ф-ции. Т.е. если процедура Test вздумает сохранить свой аргумент E где-нибудь в Button1.OnClick, то в дальшейшем при клике по этой кнопке все рухнет нафиг.


Это понятно. Тут бы как-нибудь обеспечить корректность самого вызова (убрать явные приведения типов) и можно было бы расслабиться :)

По поводу вашего примера, да конечно, я согласен. Что для не-методов и без ассемблера будет работать. Только вот уже с методами, как я понимаю, нужна дополнительная информация (self хранить).
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Локальные лямбды. Proof of concept

Сообщение Sergei I. Gorelkin » 25.02.2010 22:18:26

MageSlayer писал(а):Если можно по-подробнее. Собственно по поводу концептуальной разницы, вопросов нет. Сымитировать ООП можно и просто на базе лямбд. Я бы назвал это просто оптимизацией, т.к. вместо кучи размещаем данные лямбды в стеке.


В Паскале совместимость типов не зависит от их внутренней структуры. Например, типы Pointer и PtrInt "внутри" одинаковые, но без явного приведения несовместимы друг с другом.
То есть вряд ли кто из разработчиков согласится на модификацию языка, позволяющую считать метод и вложенную процедуру совместимыми.

MageSlayer писал(а):По поводу вашего примера, да конечно, я согласен. Что для не-методов и без ассемблера будет работать. Только вот уже с методами, как я понимаю, нужна дополнительная информация (self хранить).


Процедура, вложенная в метод, не является методом. Аргумент self для нее доступен через родительский кадр стека, как и остальные аргументы внешнего метода.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1395
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Локальные лямбды. Proof of concept

Сообщение SII » 25.02.2010 22:48:22

Sergei I. Gorelkin писал(а):
MageSlayer писал(а):В Паскале совместимость типов не зависит от их внутренней структуры. Например, типы Pointer и PtrInt "внутри" одинаковые, но без явного приведения несовместимы друг с другом.
То есть вряд ли кто из разработчиков согласится на модификацию языка, позволяющую считать метод и вложенную процедуру совместимыми.


Так и нельзя соглашаться, ведь одно из наиболее важных преимуществ Паскаля над Си -- это достаточно жёсткая типизация, предотвращающая целую кучу потенциальных ошибок.
SII
новенький
 
Сообщения: 64
Зарегистрирован: 24.06.2007 17:15:09
Откуда: Зеленоград

Re: Локальные лямбды. Proof of concept

Сообщение MageSlayer » 26.02.2010 00:32:42

Sergei I. Gorelkin писал(а):В Паскале совместимость типов не зависит от их внутренней структуры. Например, типы Pointer и PtrInt "внутри" одинаковые, но без явного приведения несовместимы друг с другом.
То есть вряд ли кто из разработчиков согласится на модификацию языка, позволяющую считать метод и вложенную процедуру совместимыми.


Понятно. Значит остается писать по-старинке в виде хаков :(.
Или патчить компилятор самостоятельно :)

Кстати, кто-нибудь знает - зачем Вирт спроектировал вложенные функции, если как обычные функции они себя не ведут? Как-то с наскока я не нашел на ответа на этот вопрос.
И зачем Борланд реализовала ... of object вместо унификации типов процедур?

Sergei I. Gorelkin писал(а):Процедура, вложенная в метод, не является методом. Аргумент self для нее доступен через родительский кадр стека, как и остальные аргументы внешнего метода.

Да, похоже на то.

Спасибо за пояснения.

Добавлено спустя 6 минут 54 секунды:
SII писал(а):Так и нельзя соглашаться, ведь одно из наиболее важных преимуществ Паскаля над Си -- это достаточно жёсткая типизация, предотвращающая целую кучу потенциальных ошибок.


К чему это?
Я как раз строгой типизации и хочу. Суть хаков как раз и заключается в том, что "жесткая" типизация упорно отказывается видеть во вложенной функции - функцию. Как я понимаю, в Object Pascal даже отдельного типа такого нет - вложенная функция (local как называет ее компилятор).
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Локальные лямбды. Proof of concept

Сообщение alexrayne » 26.02.2010 03:19:01

по поводу реализации замыканий в паскале было обсуждение на фрипаскалевом форуме. посмотрите здесь.
http://community.freepascal.org:10000/b ... 082#341116
впринципе я реально реализовывал замыкание в стиле турбопаскаля - с использованием get_caller_frame(get_frame) в качестве контекста подпроцедуры - 1й параметр враппера (собсно чтото подобное тому что Вы привели в 1х постах, но без использования ассемблера). оно вроде работает даже с оптимизацией, только дебагер может облажаться на 1м параметре.
Марко предложил на ето использовать более безопасный подход - вместо подпроцедуры размещать в стеке локальных параметров объект с методом. впринципе етот подход хорош, но требует больше возни + создание\освобождение объекта.

я в томже форуме предлагал Марко сэмулировать на уровне РТЛ компилятора типы локальных процедур как методы объекта - контекста процедуры. тобиш надо сделать в РТЛ зарезервированый объект например CONTEXT, и проецировать локальные процедуры (а может еще и переменные) в его методы. потом методы етого объекта можно передавать как обычные методы - procedure(...) of object
этот путь потребует только расширения РТЛ без наворачивания\дополнения самого синтаксиса языка. ессно предложение даже нерассматривалось.
alexrayne
постоялец
 
Сообщения: 125
Зарегистрирован: 03.12.2008 16:56:26

Re: Локальные лямбды. Proof of concept

Сообщение MageSlayer » 26.02.2010 10:59:04

alexrayne писал(а):по поводу реализации замыканий в паскале было обсуждение на фрипаскалевом форуме. посмотрите здесь.
http://community.freepascal.org:10000/b ... 082#341116
впринципе я реально реализовывал замыкание в стиле турбопаскаля - с использованием get_caller_frame(get_frame) в качестве контекста подпроцедуры - 1й параметр враппера (собсно чтото подобное тому что Вы привели в 1х постах, но без использования ассемблера). оно вроде работает даже с оптимизацией, только дебагер может облажаться на 1м параметре.


Судя по всему все так и делают. Молча :)

alexrayne писал(а):Марко предложил на ето использовать более безопасный подход - вместо подпроцедуры размещать в стеке локальных параметров объект с методом. впринципе етот подход хорош, но требует больше возни + создание\освобождение объекта.

Именно, от лишней писанины и пытаемся уйти.

alexrayne писал(а):я в томже форуме предлагал Марко сэмулировать на уровне РТЛ компилятора типы локальных процедур как методы объекта - контекста процедуры. тобиш надо сделать в РТЛ зарезервированый объект например CONTEXT, и проецировать локальные процедуры (а может еще и переменные) в его методы.
потом методы етого объекта можно передавать как обычные методы - procedure(...) of object


... + проекцировать параметры внешней функции.

alexrayne писал(а):этот путь потребует только расширения РТЛ без наворачивания\дополнения самого синтаксиса языка. ессно предложение даже нерассматривалось.

По поводу RTL, что-то я не понял. Куда вы собираетесь положить этот код?
Беглый просмотр rtl/unix, rtl/linux тоже ничего похоже не дал.
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Локальные лямбды. Proof of concept

Сообщение MageSlayer » 03.04.2010 00:23:57

Кстати, вот еще одна попытка решения проблемы вложенных функций - http://bugs.freepascal.org/view.php?id=15925
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

След.

Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 41

Рейтинг@Mail.ru