Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.134.118.95] |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Туплю!
непойму как сохранить адреса виртуальных фунций. Выбрасывает ошибку 'Incompatible types: 'System.SysUtils.TFunc<System.TFunc<System.string>' and 'string'' type IVoice = interface function Voice: string; end; TAnimal = class abstract (TInterfacedObject) private FName: string; public property Name: string read FName write FName; end; TDog = class(TAnimal, IVoice) public function Voice: string; end; TCat = class(TAnimal, IVoice) public function Voice: string; end; { TDog } function TDog.Voice: string; begin Result:= 'Arf-Arf!'; end; { TCat } function TCat.Voice: string; begin Result:= 'Meow-Meow!'; end; var voices: TArray<IVoice>; funcs: TArray<TFunc<string>>; I: Integer; begin voices:= [TDog.Create, TCat.Create, TDog.Create]; SetLength(funcs, Length(voices)); for I := 0 to High(voices) do funcs[i]:= voices[i].Voice; //<--- это вызов, а надо сохранить адрес функции Readln; end. Хелп плз! |
Сообщ.
#2
,
|
|
|
А вот если убрать интерфейс IVoice и заюзать абстрактный класс TAnimal то все пашет, адреса сохраняются!
type TAnimal = class abstract private FName: string; public function Voice: string; virtual; abstract; property Name: string read FName write FName; end; TDog = class(TAnimal) public function Voice: string; override; end; TCat = class(TAnimal) public function Voice: string; override; end; { TDog } function TDog.Voice: string; begin Result:= 'Arf-Arf!'; end; { TCat } function TCat.Voice: string; begin Result:= 'Meow-Meow!'; end; var animals: TArray<TAnimal>; funcs: TArray<TFunc<string>>; I: Integer; begin animals:= [TDog.Create, TCat.Create, TDog.Create]; SetLength(funcs, Length(animals)); for I := 0 to High(animals) do funcs[i]:= animals[i].Voice; //<--- а вот так все пучком! // тест for I := 0 to High(funcs) do Writeln(funcs[i]()); Readln; end. Я в непонятках Где я не доучил Объект Паскаль? Кстати учу по книжке моего кента Марко Кента |
Сообщ.
#3
,
|
|
|
Cfon
Это косяк эмбаркодеров. Они толком не за документировали и как следствие толком не закодировали. |
Сообщ.
#4
,
|
|
|
Цитата Pavia @ Cfon Это косяк эмбаркодеров. Они толком не за документировали и как следствие толком не закодировали. Решил их косяк через анонимку begin voices:= [TDog.Create, TCat.Create, TDog.Create]; SetLength(funcs, Length(voices)); for I := 0 to High(voices) do (procedure (i: integer) //<--- iife funcs[i]:= function: string //<--- anonimous function begin Result := voices[i].Voice; end; end)(i); for I := 0 to High(funcs) do Writeln(funcs[i]()); Readln; end. в JS стиле begin var voices: TArray<IVoice>:= [TDog.Create, TCat.Create, TDog.Create]; var funcs: TArray<TFunc<string>>; SetLength(funcs, Length(voices)); for var I := 0 to High(voices) do (procedure (i: integer) begin funcs[i]:= function: string begin Result := voices[i].Voice; end; end)(i); for var I := 0 to High(funcs) do Writeln(funcs[i]()); Readln; end. похоже тока в JS не надо указывать типы да и память не надо в ручную выделять ПС. Меня за уши теперь от Делфи не отянуть Формочки набиваются на раз два + код паскаля простой, легко читаемый, это просто мечта прогера |
Сообщ.
#5
,
|
|
|
for var I := 0 to High(voices) do (procedure (i: integer) //<--- iife funcs[i]:= function: string //<--- anonimous function begin Result := voices[i].Voice; end; end)(i); Отдельно хочу пояснить про iife (самовызываемая функция) во втором цикле for, она создает локальную область видимости. Зачем? Затем чтобы анонимная функция сохранила переменную i в ней! Я хз почему, но анонимка не замыкает локальную переменную i цикла for! Мои эксперементы с ней показали что она удалятся после цикла В итоге если не создать вручную локалку мы имеем эксепшен при доступе к массиву funcs. Наверно очередной косяк Эмбаркадэро А вот на JS найди отличия for (var i=0; i < voices.length; i++) (function (i) { //<--- iife funcs.push( function () { //<--- anonimous function return voices[i].Voice; } })(i); |
Сообщ.
#6
,
|
|
|
Цитата Cfon @ Мои эксперементы с ней показали что она удалятся после цикла В итоге если не создать вручную локалку мы имеем эксепшен при доступе к массиву funcs. Тикет qc создал надеюсь? Я б проголосовал. |
Сообщ.
#7
,
|
|
|
Цитата jack128 @ Цитата Cfon @ Мои эксперементы с ней показали что она удалятся после цикла В итоге если не создать вручную локалку мы имеем эксепшен при доступе к массиву funcs. Тикет qc создал надеюсь? Я б проголосовал. Упс, пардон ми Я поспешил с выводами переменная i не удалятся, замыкание есть, но все 3 вызова в цикле захватывают одну и туже i и итоге при обращении у массиву функций идет обращение по индексу i=3 что ведет к эксепшену обращение за пределы массива. Похожая ситуация есть в JS тока там var i определенная в цикле имеет глобальную область действия, а в Delphi она локальная. Короче тут все без косяков, это просто фича цикла for и анонимок. Переменные захватываются по ссылке, а не по значению, чтобы решить траблу надо заюзать iife создавая на каждой итерации новую локалку. В JS ввели let для определения локальной переменной, как она работает я не разбрал, кажись анонимки захватывают ее по значению точно не помню, потому в JS можно решить это так for (let i=0; i < voices.length; i++) // iife не надо ибо захват i по значению funcs.push( function () { return voices[i].Voice; } |
Сообщ.
#8
,
|
|
|
Цитата Cfon @ Я поспешил с выводами переменная i не удалятся, замыкание есть, но все 3 вызова в цикле захватывают одну и туже i и итоге при обращении у массиву функций идет обращение по индексу i=3 что ведет к эксепшену обращение за пределы массива. по идее - это тоже касяк. То есть, если мы объявили i в var блоке функции - то такое поведение логично, а если мы объявили i в цикле, то захвачено должно на каждой итерации - отдельное значение. Вообще смешно это, абракадабра заимствует многое из C# при этом повторяя их же ошибки. В шарпе изначально было такое же поведение, но несколько лет назад они фиксанули это и теперь захват значения в лямбду происходит как будто у нас на каждой итерации новая переменная i с новым значением (как c let в js) |
Сообщ.
#9
,
|
|
|
Цитата funcs[i]:= voices[i].Voice; //<--- это вызов, а надо сохранить адрес функции Эээ, а так разве не то, что требуется? funcs[i]:= @voices[i].Voice; |
Сообщ.
#10
,
|
|
|
Цитата Fr0sT @ Эээ, а так разве не то, что требуется? funcs[i]:= @voices[i].Voice; так - это то что даже не компилится |
Сообщ.
#11
,
|
|
|
Ну ему нужен адрес функции - он берется вот так.
|
Сообщ.
#12
,
|
|
|
Нет. Чтобы взять адрес функции, справа от оператора @ должна быть функция, а Voices[I].voice - это метод интерфейса. в Дельфи нет возможности взять его адрес.
upd: встроенной возможности. |
Сообщ.
#13
,
|
|
|
Цитата Fr0sT @ Цитата funcs[i]:= voices[i].Voice; //<--- это вызов, а надо сохранить адрес функции Эээ, а так разве не то, что требуется? funcs[i]:= @voices[i].Voice; Ошибка [dcc32 Error] : E2036 Variable required Ну да фиг с ним Продолжаю улучшения скрываем цикл for, функциональщина так и прет type TForEachProc<T> = reference to procedure (Value: T; Index: Integer); TArrayHelper = class helper for TArray class procedure ForEach<T>(const Values: array of T; AProc: TForEachProc<T>); static; end; { TArrayHelper } class procedure TArrayHelper.ForEach<T>(const Values: array of T; AProc: TForEachProc<T>); begin for var I := 0 to High(Values) do AProc(Values[I], I); end; var voices: TArray<IVoice>; funcs: TArray<TFunc<string>>; begin voices:= [TDog.Create, TCat.Create, TDog.Create]; SetLength(funcs, Length(voices)); TArray.ForEach<IVoice>(voices, procedure (elem: IVoice; i: integer) begin funcs[i]:= function: string begin Result:= elem.Voice; end; end); // тест for var i := 0 to High(funcs) do Writeln(funcs[i]()); Readln; end. ПС. обратите внимание тут iife уже не требуется поскольку наши операторы заключены в анонимку. |
Сообщ.
#14
,
|
|
|
Улучшаем предыдущий код, убираем выделение памяти для массива funcs!
Для этого допишем TArrayHelper, а именно создадим функцию Map и заменим в коде ForEach на Map. Кто не в теме функциональной парадигмы поясню: Map получает на входе исходный массив а возвращает другой массив, попутно выполняя какие либо преобразования исходного массива. type TForEachProc<T> = reference to procedure (Value: T; Index: Integer); TMapProc<T, R> = reference to function (Value: T; Index: Integer): R; TArrayHelper = class helper for TArray class procedure ForEach<T>(const Values: array of T; AProc: TForEachProc<T>); static; class function Map<T, R>(const Values: array of T; AProc: TMapProc<T, R>): TArray<R>; static; end; { TArrayHelper } ..... class function TArrayHelper.Map<T, R>(const Values: array of T; AProc: TMapProc<T, R>): TArray<R>; begin Result:= []; for var I := 0 to High(Values) do Result:= Result + [AProc(Values[i], i)]; end; begin var voices: TArray<IVoice> := [TDog.Create, TCat.Create, TDog.Create]; var funcs := TArray.Map<IVoice, TFunc<string>>(voices, function (v: IVoice; i: integer): TFunc<string> begin Result:= function: string begin Result := v.Voice; end; end); // test for var fn in funcs do Writeln(fn()); Readln; end. В итоге никаких тебе явных выделений памяти и циклов. Обращаю внимание что при объявлении переменная funcs не указан её тип, тут работает выведение типов, а вот при объявление voices требуется указать тип ибо тут выведеный тип не подходит, поскольку нам надо TArray<IVoice>, а компилятор нам предлагает array of const, что делать? укажем вручную ПС. в мапе я заюзал конкатенацию массивов, но можно и через SetLength. |