OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Четверг, 17 Январь, 2019 11:56

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 14 ] 
Автор Сообщение
СообщениеДобавлено: Вторник, 04 Декабрь, 2018 11:43 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 334
Нам удалось достаточно разобраться в компиляторе blackboxcomponentbuilder (участвовали я, Иван Денисов и МихалНик, которого вы вряд ли знаете, а также все пользователи, отвечавшие на мои вопросы). Результат получился такой:
Код:
Печ(ITEM(TRUE)); (* Печатает Б $TRUE *)
Печ(ITEM(100500)); (* Печатает Ц 100500 *)

ITEM - это новая встроенная функция, которая берёт тип своего аргумента и на его основании подбирает функцию
NewDynamicallyTypedPtrFromЭТОТТИП, которую и вставляет вместо себя. Выражение возвращает указатель на DynamicallyTyped,
а DynamicallyTyped может сказать нам о своём типе и имеет функции AsBOOLEAN, AsINTEGER. AsINTEGER вернёт INTEGER, если тип - INTEGER, а в противном случае будет исключение.

Код - здесь https://gitlab.com/budden/nkp
В качестве побочного эффекта была произведена частичная «деобфускация», документирование и русификация кода компилятора, а также реализованы минимальные удобства для работы с исходниками в Visual Studio Code.

Другие типы пока не обрабатываются, но я уже понял, что решение через ITEM слишком уж аскетичное. И тут надо посоветоваться. На данный момент я хочу как-то так сделать:
Код:
PROCEDURE Печ(DYNAMICALLY_TYPED арг); (* формальный параметр имеет тип DynamicallyTyped, а фактический может иметь любой тип *)
BEGIN
CASE арг.тип OF
  | типINTEGER: StdLog.Int(арг.AsInteger)
... END Печ;

Печ(100500);
Печ(TRUE);

И вторую вещь хочу я сделать:
Код:
PROCEDURE Список(Элемент1 ... ЭлементЭн);
PROCEDURE СписокЧерезСвязь(Тип: STRING; ПолеСледующий: STRING; DYNAMICALLY_TYPED Элемент1 ... ЭлементЭн);

Они достаточно разные. Первая из них принимает элементы любого (разного) типа и записывает их в некие CONS-ячейки.
Вторая - принимает элементы одинакового типа. Строит односвязный список со связью через поле с именем ПолеСледующий.
Первая функция динамичная (она даёт возможность реализации JSON и прочих динамических вещей), а во второй динамизм можно полностью исключить, если сделать функцию встроенной и во время компиляции разрешать типы.
Получаются такие вот «полумакросы».

Соответственно, прошу совета - кто что подобное делал, какие подходы можно применить. Тут наблюдается быстрое разрастание
добавляемых возможностей, а хочется обойтись минимумом.

Только давайте не будем обсуждать реализацию через наследование от базовой записи. Понятно, что можно сделать наследников от Meta.Value. Но тогда будет ненужный динамизм реализации (методы извлечения придётся делать виртуальными), и конструктору нужно будет явно указать тип, а это излишне, т.к. тип любого выражения компилятору и так известен. Тут речь идёт про механизм "типизированных макросов" и в общем виде способ действий такой:

берём узлы дерева компилятора с известными типами и берём процедуру. Производим некое вычисление от этих аргументов. И получаем (во время компиляции) некую новую процедуру, которую нужно вызвать вместо заданной. Либо вставляем преобразующий вызов при передаче параметра. Это совсем другой подход, чем виртуальные методы, и эти два подхода не взаимозаменяемы. Ни один из них не лучше, но вы применяете динамику там, где должна быть статика. В С++ это делается шаблонами, которые зачастую получаются ещё ужаснее, чем динамика.

И ещё - кто-то говорил, что есть статья про "умные макросы" для Оберона. Где можно её почитать?


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 04 Декабрь, 2018 12:00 

Зарегистрирован: Пятница, 20 Июль, 2007 17:26
Сообщения: 684
Откуда: Псков
Здесь на форуме всё ищется - viewtopic.php?f=30&t=5366&start=20#p100049


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 04 Декабрь, 2018 12:35 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 334
Спасибо. Хотя мне не понравилось. Но ещё спасибо за ссылку на саму тему.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 04 Декабрь, 2018 15:15 
Аватара пользователя

Зарегистрирован: Четверг, 08 Октябрь, 2009 15:00
Сообщения: 2247
Денис, спасибо за благодарность. Но я мало причастен к твоему успеху тут :) лишь немного консультировал пару месяцев назад.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 04 Декабрь, 2018 15:52 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 334
Ну я даже хотел сделать специальную оговорку, что ты в самой ереси не участвовал, а только предоставил инструменты :) . На самом деле, без текстового формата для исходников, без возможности поиска по файлам в VS Code (и особенно поиска с заменой по многим файлам) этот результат на сегодня не был бы достигнут. Т.к. у меня не особо много свободного времени. Сочетание возможностей самого блекбокса с возможностями VS Code позволило работать намного эффективнее и достичь результата быстрее.

Так что твой вклад очень большой.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 07 Декабрь, 2018 21:16 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 260
Цитата:
PROCEDURE Список(Элемент1 ... ЭлементЭн);
[...]
В С++ это делается шаблонами, которые зачастую получаются ещё ужаснее, чем динамика.

Вместо "variadic templates" со всякими "unpacking" можно применить дженерики, есс-но если таковы существуют, вместе с адекватной системой типизации. Выше была ссылка на "макросы", там рядом предлагались и дженерики, но упрощённые и без статической типизации.

А так, к примеру, в некоторых ML-вариациях поступают примерно так:
Код:
fun tuple_str[U, V](x: U * V) =>
      match x with
      | a , b => str a +", " + str b
      end;

fun tuple_str[U, V](x: U ** V) =>
      match x with
      | a ,, b => str a +", " + tuple_str b
      end;

...
// применение:
a = tuple_str(12, 345.5444, "str1");

Тип вида "U * V" (через лог. and) есть обычный кортеж (из 2-х элементов типа U и V). Тип "U ** V" (U в степени V) есть обобщённая структура: элементы в количестве V произвольного типа U (по аналогии представления массивов вида "int ^ 3"). Оператор ",," разрезает кортеж на "голову" и "хвост" (в стиле оператора "::" для списков), хвост не используется ни для чего, кроме "нарезки" при сопоставлении и для передачи аргумента функции. На месте рекурсивного вызова возникает "инстанцирование шаблона" (рекурсивно). Такие функции должны иметь соответствующий "pattern matching" на самом верхнем уровне (который может быть выражен по-разному). И есс-но действуют необходимые (очевидные) правила перегрузки функций.

Для Паскале-подобного языка можно попробовать аналогию, без дженериков, используя древние трюки в стиле Delphi.
Не в курсе (я не активный пользователь Оберонов), возможны ли такие выкрутасы, о которых далее, но пусть в качестве бреда, во всяком случае.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 07 Декабрь, 2018 21:21 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 260
Ещё в старых Pascal/Delphi была поддержка "безтиповых" аргументов процедур вида "var v;", "const c;". А также есть и открытый массив вида "a: array of const". Последний есть runtime-решение, неявно компилятором используется универсальный вариантный тип для динамического массива (TVarRec, если память не изменяет). Вместо runtime можно попробовать реализовать статическое "инстанцирование", примерно в стиле (псевдо-Pascal):
Код:
procedure work_tuple(s: string; args: array of const);
    procedure prepare;
    begin
        ...
    end;

    procedure finish;
    begin
        ...
    end;

    procedure work(a,,b: array of const); overload;
    var m: integer;
    begin
        ...
        // используем а, конкретный тип уже известен,
        // anyproc подбирается согласно типу, как обычно:
        m := a.anyproc(n);
        ...
        work(b); // b доступно только для передачи аргумента
    end;

    //будет вызвана при пустом массиве (конец рекурсии, в т.ч. при пустом массиве изначально)
    procedure work(); overload;
    begin
      ... 
    end;

begin
  prepare;
  work(args);
  finish;
end;

// применение:
var rec: SomeRecord;
...
work_tuple("any string", 1234, 45.5454, rec);

// с пустым массивом:
work_tuple("none");

В общем, вот такие "variadic"-аргументы. Или же при наличии в языке константных выражений для массивов использование становится таким же, как и оригинальный "array of const":
work_tuple("any string", [1234, 45.5454, rec]);

В объявлениях при наличии прочих, не "variadic"-аргументов, вроде бы, различимость присутствует:
procedure work(n,m: integer; head,,tail: array of const);

Возникает необходимость в правилах перегрузки процедур с учётом "variadic". Есть смысл организовать совместимость "variadic"-массива с прочими массивами, т.е. массив можно готовить предварительно (в runtime, как обычно в программном коде) и передавать процедуре.

Идентификация типа "головы" массива уже предопределяет дальнейшее использование (статика, виртуальные вызовы, тестирование типа и т.д., т.е. всё соответствует обычным положениям в языке). Возникает потребность в унификации "объектного" вызова, в т.ч. и для базовых встроенных типов аля "a.anyproc(...)" ("всё есть объект"), для чего необходима эквивалентность форм вызова:
Код:
procedure anyproc(i, j: integer);
...
var n, m: integer;
...
anyproc(n, m);
n.anyproc(m);

Кроме массива напрашивается аналогичная поддержка и для одиночных "var v" с "const c" без типа (расширить с IN, OUT).
В идеале, при современном подходе, потенциальная реализация требует техники мономорфизации, чтобы код не разбухал.
Как-то так, в общем.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 07 Декабрь, 2018 21:26 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 260
Цитата:
PROCEDURE СписокЧерезСвязь(Тип: STRING; ПолеСледующий: STRING; DYNAMICALLY_TYPED Элемент1 ... ЭлементЭн);

Кстати, насколько я понимаю, в Оберонах нет произвольного доступа к полям структуры (когда заранее неизвестны целевые объекты или места доступа), в отличие от того же доступа к элементам массивов ("адрес" от поля не взять и не поимеешь указателя, может быть есть некие низкоуровневые средства, но, скорее всего, как unsafe). Если не ошибаюсь, в Активном Обероне, кажись, есть концепция "делегатов". Как вариант, её можно обобщить до понятия виртуальных свойств, по мотивам как в примере по ссылке ниже с использованием "объектных" функций (это простой "скриптовый" Pascal):
https://github.com/nielsAD/lape/blob/master/tests/Directive_AutoProperties.lap

Альтернативно, в ML-лях есть обобщённое понятие "проекций" над структурами (кортежами, массивами) как языковое средство. В статейке ниже есть подборка видов проекций (это "скриптовый" вариант ML, фактически, препроцессор для C++ (с получением бинарников), оттого в ходу и указатели с адресами и пр.):
https://github.com/felix-lang/felix/blob/master/pdfs/technote-row-polymorphism.pdf

Можно попробовать ввести аналог (единственный) как языковое средство для проекций над записями (с оптимизациями), которое сгодится и для метапрограммирования, как-то так:
Код:
type MyRec = record
  a: integer;
  b: string;
end;
...
var
  p: case integer of MyRec; // проекция или выбор типа integer из MyRec
  rec: MyRec;
  i: integer;
...
// получаем "адрес" поля a (обращаясь к типу)
p := MyRec.a;

// используется как процедура:
i := p(rec);  // чтение свойства/поля
p(rec, 234);  // установка значения

// получаем "адрес" поля от переменной
p := rec.a;

Тип "case <тип> of <структура>" м.б. и обобщённым:
case const of MyRec; // выбор любого типа
case integer of record; // выбор из любой записи
case const of record; // выбор любого типа из любой записи

И есс-но "case-тип" доступен и в качестве "variadic":
any_proc(MyRec.a, MyRec.b); // передаём два "поля" для процедуры с variadic-аргументами


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 07 Декабрь, 2018 21:30 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 260
А раз такая пьянка, то в принципе-то, возникает полноценное метапрограммирование.

Кроме параметров типов для объявления структур (записей) необходимы либо "ассоциированные типы" (как в ML-лях) -- ниже статейка с их обоснованием в Rust:
http://rurust.github.io/rust_book_ru/src/associated-types.html

Либо можно воспользоваться "дискриминантом" типа по мотивам как в Ada, когда параметры типа (их имена) доступны также, как и поля записи (к тому же техника "ассоциированных типов" востребована в случае наличия "классов типов" или их аналогов в виде всяких trait-ов и осуществления "инстанцирования" класса/trait-а для типа):
Код:
// Пусть параметры задаются как дискриминант в Ada --
// в обычных скобках, чтобы меньше пугать "дженериками":
type MyRec(Elem: type; Size: integer) = pointer to record
    data: array Size of Elem;
end;
...
// Объявление обобщённой процедуры (метода),
// где "а" передаётся по ссылке (компилятор
// при потребности и возможности может передавать и по значению):
procedure (t: MyRec) any_proc(in a: t.Elem);

// Объявление переменной:
var rec: MyRec(integer, 10);

И, в целом, желательно двигаться в сторону некой "упрощённой Ada", как было отмечено здесь в смежной теме. Тем более:
http://www.ethoberon.ethz.ch/native/compiler/x.operators.html
Код:
     PROCEDURE "+"* (a, b: ARRAY OF CHAR): ARRAY OF CHAR;
     VAR  res: ARRAY LEN(a) + LEN(b) + 1 OF CHAR; i, j: LONGINT;
     BEGIN
          i := 0;
          WHILE a[i] # 0X DO  res[i] := a[i]; INC(i)  END;
          j := 0;
          WHILE b[j] # 0X DO  resij] := b[j]; INC(i); INC(j)  END;
          res[i] := 0X;
          RETURN res
     END "+";

Это примерчик из расширения OberonX. Есть подозрение, что res, всё-таки, статический массив на "резиновом" стеке в стиле Ada.

В общем-то, вот такой бред в качестве альтернативы "шаблонной магии".


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 07 Декабрь, 2018 23:18 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 334
Цитата:
Не в курсе (я не активный пользователь Оберонов), возможны ли такие выкрутасы

Здесь речь идёт о форке КП, т.е. можно сделать всё, вопрос - какой ценой и как правильно.
Цитата:
Вместо runtime можно попробовать реализовать статическое "инстанцирование"

Можно, а зачем?
Следующее пока не осилил. Полноценное МП пока в цели не включаем. Приценился к тому из предлагаемого, что было названо лучшим, но оно не устроило.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 11 Декабрь, 2018 18:30 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 260
budden писал(а):
Цитата:
Вместо runtime можно попробовать реализовать статическое "инстанцирование"

Можно, а зачем?

Вы же сами в начале акцентировали на потребности в статической диспетчеризации, и тема так соответственно определена как "полиморфизм" (видимо, речь о статическом полиморфизме в дополнение к уже имеющемуся на основе динамической диспетчеризации).
budden писал(а):
Следующее пока не осилил. Полноценное МП пока в цели не включаем

Полноценное МП, скорее, это что-то вроде Scalameta или, попроще, MetaLua, с "квази-цитатами" как в Лиспах и с такими же возможностями любых выкрутасов. В принципе-то, средства надязыковые, и можно сделать аналоги для себя по-своему для любого своего языка.
Выше же были заметки насчёт концепции "безтиповых" аргументов, в т.ч. с учётом их неопределенного количества, со статической диспетчеризацией/компиляцией с возможностью избежать runtime-циклов (заменить их с оптимизациями, если возможно).

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

Хотя, с другой стороны, на форуме представлен некий новый-старый Oberon/L с отключением языковых возможностей. В Ada в дополнение для удовлетворения стандартов безопасности, вроде бы, есть возможность отключить и динамическую диспетчеризацию (фактически, отрубается ООП). В теме про Esterel есть материалы по поводу Real Time Java и Safety-Critical Java, где также может быть запрет виртуальных вызовов (но имеются и некоторые контролируемые послабления -- вся возможная иерархия классов должна быть статически идентифицирована, что позволяет иногда провести анализ runtime-затрат и т.д.).
Возможно, есть смысл проанализировать применимость языка для более-менее содержательных прикладных задач, если вдруг отрубить extensible и иже с ним связанное.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 19 Декабрь, 2018 01:11 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 334
Не сказать, что всё понял. Но как-то мне стало жабисто делать DYNAMICALLY_TYPED для списков. Например, функция format из лиспа вполне способна определить ошибочный тип прямо во время компиляции. Отказ от этого снижает качество ЯП.

И тут я вспомнил, что нужно обязательно поменять в компиляторе: состояние лексера является глобальным, поэтому нельзя развязать этапы компиляции между собой. В момент возникновения ошибки "текущая лексема" должна хранить то место, где ошибка произошла. Из-за этого этапы компиляции у Вирта дико перемешаны. Значит, нужно прежде всего сделать состояние лексера записью и весь контекст хранить в этой записи.

Тогда вместо "ошибка" можно будет сказать "ошибка в лексеме" и дать лексему. После этого можно будет начать развязывать узлы компилятора.

Но это я отвлёкся. Какие у нас варианты использования полиморфизма, которые точно нужны?

1. тип вариант, способный вместить любое. Зачем? Для REPL - там бывает переменная типа * в лиспе. Она имеет тип "любое".
2. функция, способная принять любой объект. Зачем? print
3. функция, способная принять любое кол-во любых объектов. Зачем? лисповый список, json.
4. функция, способная принять в качестве параметра тип. Такие функции уже есть в КП: MIN, MAX, SIZE. Значит, сам по себе механизм "вызова типа по имени" в языке есть. Нет оснований не дать программисту возможность использовать его в полную силу.
5. функция, к-рая строит список из заданных объектов односвязный список данного типа, со связью по полю данного типа. Я приводил пример выше. Это гибрид из обычной ф-ии, и ф-ии, способной принять любое кол-во любых объектов. Т.е. часть параметров проверяется, а часть - любые.
6. Функция, принимающия имя типа записи, значения полей, и строящая экземпляр записи с этими значениями. Первый аргумент - типа "тип", а последующие - типа "что угодно". НО! Уже во время компиляции можно определить ошибки типизации, потому что последующие, на самом деле - не типа "что угодно", а типа, соответствующего данному полю в данной записи. Зачем это? Для сериализации (аналог #S в лиспе).

Итак, что же отвечает всем этим требованиям? Была такая идея:
Вводим понятие "макро-функция". Такая ф-я принимает все параметры в том виде, в котором их знает компилятор. Далее она должна из дерева ИмяМакроФункции(арг1,арг2,аргN)
построить другое дерево:

КакаяТоДругаяФункция(ФункцияАдаптер1(арг1),ФункцияАдаптер2(арг1),ФункцияАдаптерN(аргN));

Здесь КакаяТоДругаяФункция и ФункцияАдаптерi вычисляются телом макро-функции. Ограничение состоит в том, что они должны принадлежать явно проимпортированным модулям.

Полученное дерево снова подвергается контролю на правильность типов.

Но так нельзя сделать функцию LIST из лиспа, т.к. придётся делать MAX-ARG-COUNT вариантов конструктора списка - это явный бред. Значит, здесь не хватает механизма для обработки произвольного числа аргументов. И есть сомнения в том, что нужны разные функции-адаптеры. Может быть, нужен только "тривиальный" адаптер, к-рый просто передаёт аргумент, и второй адаптер ITEM. С т.з. эффективности это не фонтан (например, теряем возможность оптимизировать константы), зато с точки зрения простоты это лучше (теряем возможность влететь из-за того, что константа 2 обрабатывается иначе, чем переменная, равная 2 - такие вещи лучше обрабатывать общими механизмами частичных вычислений, как мне кажется, но это уже дикие дебри и пора спать).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 19 Декабрь, 2018 01:19 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 334
Как вариант:

- первые ЭН параметров имеют фиксированный тип, заданный в сигнатуре макро-функции
- последний параметр может иметь тип "грязный хвост", что значит "любое кол-во любых аргументов".
Типа &rest. Макро-функция должна либо подставить эти параметры в вызываемую функцию один-за-одним, либо построить из них выражение вида Адаптер0(хв0,Адаптер1(хв1,АдаптерЭн(хвЭн,NIL))).
Адаптер0 ... АдаптерЭн вычисляются.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 19 Декабрь, 2018 17:18 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 334
Хотеть не вредно, но надо быть проще, т.к. наш проект низкобюджетный. Значит, нужно два новых "системных флага" для параметра или что-то в этом роде:

DYNAMICALLY_TYPED - оборачивает фактический параметр в выделенный (на куче?) вариант, подобно тому, как это делает ITEM. Внутри процедуры имеем дело уже с вариантом.

DYNAMICALLY_TYPED_&REST - преобразует все остальные параметры в список или массив вариантов.
Количество параметров - произвольное. В простейшем случае, такой параметр несовместим с другими параметрами и может присутствовать в сигнатуре только в одиночестве.

Такое, наверное, получится сделать и без революций. У нас не будет при этом статического контроля типов и будет выделение в куче. Но это придётся потерпеть.


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 14 ] 

Часовой пояс: UTC + 3 часа


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

Сейчас этот форум просматривают: Vlad SM, Иван Денисов и гости: 1


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Вся информация, размещаемая участниками на конференции (тексты сообщений, вложения и пр.) © 2005-2019, участники конференции «OberonCore», если специально не оговорено иное.
Администрация не несет ответственности за мнения, стиль и достоверность высказываний участников, равно как и за безопасность материалов, предоставляемых участниками во вложениях.
Без разрешения участников и ссылки на конференцию «OberonCore» любое воспроизведение и/или копирование высказываний полностью и/или по частям запрещено.
Powered by phpBB® Forum Software © phpBB Group
Русская поддержка phpBB