Спасибо! Вы навели меня на возможное решение. Помучившись немного и прочитав пару соседних веток про функциональные переменные и объекты, реализовал-таки решение через обероновское ООП. Наконец, понял, как это вообще работает. До этого про ООП у меня были мифические представления.
Короче, теперь интерфейс выглядит так (в стиле oo2c):
Код:
MODULE RKrec;
TYPE
State* = POINTER TO ARRAY OF LONGREAL;
DynamicalSystem* = POINTER TO DynamicalSystemVariables;
DynamicalSystemVariables* = RECORD
(* May be extended to include external parameters *)
state*: State;
PROCEDURE (ds: DynamicalSystem) ComputeRHS*(result: State);
(* Must be redefined by the target application, for now does nothing *)
PROCEDURE (ds: DynamicalSystem) GenerateK(VAR k1: State; VAR k3: State; VAR k4: State; VAR k5: State; VAR k6: State; h: LONGREAL; generateK6: BOOLEAN);
PROCEDURE (ds: DynamicalSystem) UpdateRK4K(k1: State; k3: State; k4: State; k5: State; step: LONGREAL);
PROCEDURE (ds: DynamicalSystem) UpdateRK4*(step: LONGREAL);
(* An application of the Runge-Kutta 4 method with time-step 'step'.
The array of state and of the right hand side functions must be
defined in the calling application *)
PROCEDURE (ds: DynamicalSystem) UpdateRKF45*(VAR step: LONGREAL; VAR oldStep: LONGREAL; rtol: LONGREAL; atol: LONGREAL);
(* An application of the Runge-Kutta-Fehlberg 45 method with the initial
time-step guess 'step'. The array of state and of the right hand
side functions must be defined in the calling application.
Internally RK4 and RK5 are performed and the result is compared. If
significantly different (w.r.t. to the tolerance), 'step' is
updated. The new 'step' estimate (for the next application) is
returned alongside with the updated 'state'. The actually used step
during the 'state' update is returned in 'oldStep'. 'rtol' is the
relative error tolerance, 'atol' is the absolute error tolerance *)
END;
END RKrec.
где все процедуры теперь связаны с соответствующими записями, а вычисление правых частей — пустая процедура. В целевой же программе я расширяю тип DynamicalSystem путём добавления полей с нужными параметрами и объявляю новый тип указателя на такие переменные (т. е. сужаю класс, тут забавная дуальность: расширение записи приводит к сужению класса) и переопределяю вычисление правых частей сообразно задаче:
Код:
MODULE test2;
IMPORT RK := RKrec, Out;
CONST
step = 1.0;
parameter = 0.5;
TYPE
DynamicalSystem = POINTER TO DSVars;
DSVars = RECORD (RK.DynamicalSystemVariables)
parameter: LONGREAL;
END;
VAR
ds: DynamicalSystem;
PROCEDURE (ds: DynamicalSystem) ComputeRHS*(result: RK.State);
VAR i: LONGINT;
BEGIN
FOR i := 0 TO LEN(ds.state^)-1 DO;
result[i] := ds.state[i]*ds.parameter;
END;
END ComputeRHS;
BEGIN
NEW(ds); NEW(ds.state,2);
ds.parameter := parameter;
ds.state[0] := 1.0;
ds.state[1] := 2.0;
Out.LongReal(ds.state[0],5,0); Out.Ln;
Out.LongReal(ds.state[1],5,0); Out.Ln; Out.Ln;
ds.UpdateRK4(step);
Out.LongReal(ds.state[0],5,0); Out.Ln;
Out.LongReal(ds.state[1],5,0); Out.Ln; Out.Ln;
END test2.
и всё работает. oo2c зачем-то заставил меня экспортировать переопределённую процедуру ComputeRHS, но, вообще-то, в Обероне-2 это не требуется (ofronto-подобные компиляторы нормально работают и без этого). Так вот ты какое, ООП.
Шёл я к этому неделю. Спасибо за наводку. Раньше не имел дел с ООП. Сначала утонул в чужеродной терминологии. Потом открыл книжку Object-Oriented Programming in Oberon-2, и стало более-менее понятно.
Кстати, вернёмся к офронту+. Мне кажется очень удобным то, что утилита oo2c для чтения интерфейсов показывает комментарии (но только особые комментарии — начинающиеся с (** ). showdef этого не делает. Например, в случае выше пришлось бы лезть в исходный текст, чтобы понять, что требуется от целевой программы.
И ещё один вопрос. А в Обероне-2 в принципе запрещено присваивание переменных типа массива или только не реализовано (что в oo2c, что в офронто-подобных)? Вроде бы, из описание языка это напрямую не следует. В Обероне-07 присваивание массивов разрешено (реализовано, например, в obnc), правда, там и массивы все с заранее известной длиной. А то приходится городить специальную процедуру для копирования.