OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Четверг, 28 Март, 2024 17:58

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




Начать новую тему Ответить на тему  [ Сообщений: 76 ]  На страницу 1, 2, 3, 4  След.
Автор Сообщение
СообщениеДобавлено: Вторник, 04 Декабрь, 2018 11:43 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 1557
Нам удалось достаточно разобраться в компиляторе 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
Сообщения: 710
Откуда: Псков
Здесь на форуме всё ищется - viewtopic.php?f=30&t=5366&start=20#p100049


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

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


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

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


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

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

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


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
Цитата:
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
Сообщения: 473
Ещё в старых 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
Сообщения: 473
Цитата:
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
Сообщения: 473
А раз такая пьянка, то в принципе-то, возникает полноценное метапрограммирование.

Кроме параметров типов для объявления структур (записей) необходимы либо "ассоциированные типы" (как в 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
Сообщения: 1557
Цитата:
Не в курсе (я не активный пользователь Оберонов), возможны ли такие выкрутасы

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

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


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
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
Сообщения: 1557
Не сказать, что всё понял. Но как-то мне стало жабисто делать 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
Сообщения: 1557
Как вариант:

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


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

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

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

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

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


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 24 Январь, 2019 18:49 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
Посмотрел предлагаемую здесь на форуме статейку насчёт дженериков для новой версии Go (по второй ссылке -- подробнее):
https://go.googlesource.com/proposal/+/master/design/go2draft-generics-overview.md
https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md

Удивило предлагаемое решение для variadic-параметризации (о чём выше отмечено здесь в теме). Как-то уж несерьёзно, что ли, сегодня действовать "в лоб" по-классически, создавая тупые заготовки-типы в стиле "тип1 для оного параметра, тип2 для двух и т.д.":
https://go.googlesource.com/proposal/+/ ... md#metrics
Код:
package metrics

import "sync"

contract comparable(v T)  {
    v == v
}

contract cmp1(T) {
    comparable(T) // contract embedding
}

type Metric1(type T cmp1) struct {
    mu sync.Mutex
    m  map[T]int
}

func (m *Metric1(T)) Add(v T) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[T]int)
    }
    m[v]++
}

contract cmp2(T1, T2) {
    comparable(T1)
    comparable(T2)
}

type key2(type T1, T2 cmp2) struct {
    f1 T1
    f2 T2
}

type Metric2(type T1, T2 cmp2) struct {
    mu sync.Mutex
    m  map[key2(T1, T2)]int
}

func (m *Metric2(T1, T2)) Add(v1 T1, v2 T2) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[key2(T1, T2)]int)
    }
    m[key(T1, T2){v1, v2}]++
}

contract cmp3(T1, T2, T3) {
    comparable(T1)
    comparable(T2)
    comparable(T3)
}

type key3(type T1, T2, T3 cmp3) struct {
    f1 T1
    f2 T2
    f3 T3
}

type Metric3(type T1, T2, T3 cmp3) struct {
    mu sync.Mutex
    m  map[key3(T1, T2, T3)]int
}

func (m *Metric3(T1, T2, T3)) Add(v1 T1, v2 T2, v3 T3) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[key3]int)
    }
    m[key(T1, T2, T3){v1, v2, v3}]++
}

// Repeat for the maximum number of permitted arguments.

[...]

// Using this package looks like this:

import "metrics"

var m = metrics.Metric2(string, int){}

func F(s string, i int) {
    m.Add(s, i) // this call is type checked at compile time
}


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 24 Январь, 2019 18:53 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
Новые Go-шные "контракты" (в стиле плюсовых концептов в предполагаемом начальном виде) пока вызывают вопросы. К примеру, непонятно как задать абстрактный тип, скажем, вещественный без конкретики. Указать в контракте преобразование (приведение) к какому-то вещественному значению -- вроде бы возникает иной контекст (в таком случае подразумевается именно возможность (совместимость) преобразование значения). Указать конкретное значение (константу) вида "v = 0.0" -- вроде бы также возникает иной контекст (допустимость конкретного значения, в случае указания двух значений (в виде двух операций присваивания) подразумевается какое-то "непонятное понятие" диапазона значений). В общем, необходимо ещё разбираться (м.б. и разработчикам также).

А в целом, с дженериками Go-шная система типизации существенно "усиливается", для полностью "безтиповых" аргументов (без контракта) допустимы лишь ограниченные, предельно общие над типами операции. К примеру, вроде бы как, без указания "протокола о намерениях" произвольное преобразование или тестирование типа не произвести, что существенно разнится от обероновской методики применения универсальных ANYPTR, ANYREC да и прочих приёмов "динамической типизации".

Для Оберон-семейства в контексте "смотрите, от чего я ради вас уклонился" -- отсутствие дженериков или их какая-то альтернатива и связанная с ними сложность. В самом деле, на сложности полно акцента в тех же статьях про дженерики в Go по ссылкам выше. Попытка упростить (прежде всего с оглядкой на С++) и подтолкнула Go-шников к такому дизайну (в котором, к слову, возникает теперь сплошь и рядом необходимость в "структурной эквивалентности" между контрактами, т.е. один и тот же метод и пр. "требуют" или могут "требовать" множество контрактов, собственно то, сплошная "статическая утиная" типизация в целом характерна в Go).
В контексте же "тотальной статической типизации" (где-то здесь на форуме определен какой-то такой тезис) -- предоставляются разработчику в пользование runtime-средства (плюс встроенные системные) для диагностики с крушением программы/системы в случае нарушений, что, конечно же, лучше неопределенного поведения.
С другой стороны, системы со "сложными дженериками" пытаются приобрести (с 70-х годов, в статьях выше про Go-шные дженерики есть исторический обзор) в том числе и "тотальность" иного плана -- максимально выявлять проблемы ещё в compiletime в меру потенциальных возможностей и минимизация потребности в runtime-проверках (плюс вопросы эффективности, производительности и пр.).

И вопрос "как лучше организовать полиморифзм?" на самом деле не простой, однако.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 24 Январь, 2019 18:57 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
По ходу дела вспоминаются предлагаемые здесь на форуме решения для "STL". Любопытно это решение:
https://forum.oberoncore.ru/viewtopic.php?f=47&t=6234

, которое, по сути то, есть обман системы типизации благодаря использованию средств runtime-рефлексии из-за необходимости доступа из базового типа к полям, которые определяются в потомках (расширениях) -- в системе типов нет никакого понятия что-то вроде абстрактных полей, контракт типов молчит о таких приёмах и, фактически, в итоге вводит в заблуждение (особенно таких как я, плотно незнакомых с рефлексией. И, к слову, обычно "report-ы о языке" не содержат сведений о средствах метаинформации как важного языкового аспекта).
В целом, средства рефлексии подталкивают (или, как минимум, не предотвращают) к стилю "ruby on rails" -- это когда активно используются "устные" соглашения вида "Container.FindBySomeID(value)", где у некоторого контейнера данных вызывается метод с определенным именем задаваемого формата, в частности: найти элемент, у которого поле SomeID равно такому-то значению. Т.е. в runtime форматируемые имена методов и прочих элементов языковых структур приобретают определенные роли (возможна и динамическая компиляция/создание элементов, если языковая среда позволяет, или каким-то способом динамически устанавливаются/реализуются роли в элементах), о ролях и форматах "договариваются" вне системы типизации. С одной стороны якобы простота и удобство, с другой "проверка контрактов" полностью в runtime (самостоятельная, т.е. задаваемая разработчиками) с соответствующими последствиями (при критической массе таких договорённостей (которых ещё нужно знать) разбираться уже трудно, тем более когда структура "договоров" возникает в runtime).

Обращает внимание это решение:
https://forum.oberoncore.ru/viewtopic.php?f=28&t=6122

, где в презентации как раз отмечены возникающие особенности от недостаточно развитых средств полиморфизма -- либо необходимость прибегать к косвенному уровню доступа с соответствующими последствиями (через указатели, с возможным тестированием/преобразованием типа и runtime-затратами, вероятна нерациональная пообъектная аллокация в памяти), либо возникает обязательность (т.е. не просто возможность изменить реализацию по умолчанию необходимых операций) в определении вспомогательных методов для каждого "прикладного" типа (такое явление в целом не соответствует в полной мере понятию полиморфного алгоритма).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 24 Январь, 2019 18:59 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
И ещё насчёт "STL". Обратил внимание на последние доклады Дня Оберона, в частности от Ильи Ермакова, где как-то вскользь косвенно представлены (однако, лишь мелькают на экране) некие "токены" и/или "тэги" (которые, к слову, вызывают недопонимание у участников конференции). Насколько я понимаю, в терминах СУБД, это некие домены как характеристики, определенные роли для типа (плюс возможность их сопоставления (равенство/сопоставимость типов), т.к. в runtime существуют переменные-singleton-ы соответствующего типа). Возможно существует и некая "STL" на таком базисе (м.б. даже без закулисной рефлексии), но на всякий случай. В таком стиле (плюс расширение "доменов" до их понимания как поля структур) мне когда-то попалась реализация "STL" для Freepascal/Delphi, когда там ещё не было дженериков -- библиотека AntiDOT (кажись, на форуме где-то уже была ссылка):
https://sourceforge.net/projects/adot/

Название AntiDOT понимается как "против DotNet", в смысле в целом против дизайна в Net фундаментального функционала (который тогда начал проникать в Delphi/Pascal).
Стиль тамошних "токенов" был (собственно, и есть) такой:
Код:
  General format of ADOT type construction:
    <DataType>.Create(<ContainerType> [,Field1Type [,Field2Type ...]])
  where
    <DataType> is any data-type class (TTString, TTByte, TTPointer, TTInteger, ...)
    <ContainerType> is any container class (TCVector, TCList, TCMap, TCHeap, ...)

  Examples:
    TTInteger.Create(TCSet)                         // set of integers
    TTWideString.Create(TCMap, [TTInteger]);        // map of string keys with integer values
    TTDouble.Create(TCVector, [TTDouble, TTString]) // vector of doubles with two additional fields
    TTString.Create(TCSet, [TTByte])                // set of strings with one additional field
    ...

  You don't have to remember all data-type classes. ADOT supports ALL Delphi
  built-in data types. Just write TT<type> where <type> is what you need
  (TTByte, TTString, TTInteger, TTDouble, ...)
...

// demonstrates creating of container with one field (TCVector, TCSList,
// TCDList, TCPriorityList, TCStack, TCDeque, ...)
procedure Demo1;
var
  n : TTInteger;
  m : TTDouble;
begin
  writeln;
  writeln('Vector of integers');
  n := TTInteger.create(TCVector);
  n.add([1,2,3,7,6,5]);
  n.Println(ffDefault, true);
  n.free;

  // Some containers MUST have two fields at least: TCMap, TCMultimap,
  // TCUnsortedMap. But we can add any number of additional fields to
  // any container!
  n := TTInteger.create(TCList, [TTDouble]);
  m := n.fields[1] as TTDouble;
  n.AddRecord([1, 0.999]);
  n.AddRecord([2, 1.785]);
  n.AddRecord([3, 3.1415926]);
  m[n.Add(4)] := 4.12;
  m[n.Add(5)] := 5.07;
  writeln;
  writeln('List with two fields');
  n.println;
  n.free;
end;

// demonstrates creating of container with two field (TCMap, TCMultimap,
// TCUnsortedmap)
// for mapping of doubles to strings
procedure Demo2;
var
  n : TTDouble;
  s : TTString;
begin
  writeln;
  writeln('Multimap(key: double; value: string)');
  n := TTDouble.create(TCMultimap, [TTString]);
  s := n.Fields[1] as TTString;

  // set some values with .Map property
  n.Map[3.14] := 'Pi';
  n.Map[2.72] := 'E';

  // another way to add data
  // now we have full checking of types in compile-time
  s[n.Add(1.618)] := 'Golden Ration';
  s[n.Add(1.618)] := 'Golden';

  // reassign
  n.Map[3.14] := 'pi';
  s[n.Find(2.72)] := 'e';

  n.Println;
  n.free;
end;

// demonstrates priority queue
procedure Demo5;
var
  n : TTInteger;
  f : TTString;
  h : TCHandle;
begin
  writeln;
  writeln('Priority queue');
  n := TTInteger.create(TCHeap, [TTString]);
  f := n.Fields[1] as TTString;
  f[n.Add(5)] := 'Five';
  f[n.Add(3)] := 'Three';
  f[n.Add(7)] := 'Seven';
  f[n.Add(0)] := 'Zero';
  f[n.Add(2)] := 'Two';
  f[n.Add(1)] := 'One';
  while n.Count>0 do
  begin
    h := n.FindMin;
    writeln(n[h], '->', f[h]);
    n.RemoveMinValue;
  end;
  n.Println;
  n.free;
end;

procedure Demo1;
var
  n: TTInteger;
  h: TCHandle;
  i,j: integer;
begin
  writeln;
  writeln('Vector of integers');
  n := TTInteger.create(TCVector);
  n.add([1,2,3,7,6,5]);

  // correct way to access items for any container
  h := n.First;
  for i:=0 to n.Count-1 do
  begin
    write(n[h], ' ');
    n.Next(h);
  end;
  writeln;

  // another way
  h := n.First;
  while h<>-1 do
  begin
    write(n[h], ' ');
    n.Next(h);
  end;
  writeln;

  // one more way
  h := n.First;
  while h<>-1 do
    write(n[n.MoveNext(h)], ' ');
  writeln;

  // accessing items by index (correct only for TCVector)
  for i := 0 to n.Count-1 do
    write(n[i], ' ');
  writeln;

  // accessing items by index (correct for all containers)
  for i := 0 to n.Count-1 do
    write(n[n.handles[i]], ' ');
  writeln;

  // find sum
  j := 0;
  for i := 0 to n.Count-1 do
    inc(j, n[n.handles[i]]);
  writeln('sum: ', j);

  n.free;
end;


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 24 Январь, 2019 19:01 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
Выше в примерчиках возникает некая инверсия дизайна на фоне распространённых решений в других языках -- вместо прямого доступа к контейнеру данных правят бал "токены". В результате, в самом деле, удобно и просто, как с обычными массивами, с контролем типов.
Однако, обратная сторона медали. Всё-таки, без дженериков нет полного контроля типов (в смысле ряд согласований типов происходит "вручную" в runtime). В оригинале полно оптимизаций на ассемблере (в т.ч. и для обхода языковых ограничений/особенностей, лишнего системного runtime-контроля), но такие приёмы, всё же, есть обман языковых средств.

Для своих же типов-"токенов" необходимо уже самостоятельно организовывать обвёртки в таком стиле (в данном случае с косвенным доступом, или же необходимо создавать свою реализацию типа-столбца с хранением данных по месту внутри контейнера по аналогии с типами-столбцами ("токенами") над встроенными базовыми типами):
Код:
procedure TestArrayOfPointers;
var
  p: TTPointer;
  i: integer;
  sum, n: double;
begin
  writeln('test vector of Pointer');
  p := TTPointer.create(TCVector);

  // alloc memory for items
  for i := 0 to 999 do
    p.Add(allocmem(SizeOF(double)));

  // fill allocated memory with random values and find SUM
  sum := 0;
  for i := 0 to p.count-1 do
  begin
    n := random*1000;
    sum := sum + n;
    double(p[i]^) := n;
  end;

  // check sum
  n := 0;
  for i := 0 to p.count-1 do
    n := n + double(p[i]^);
  assert(n=sum);

  // free allocated memory
  for i := 0 to p.count-1 do
    FreeMem(p[i]);

  p.free;
  writeln('ok');
end;


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 24 Январь, 2019 19:05 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 473
Есть смысл оценить (в сопоставлении с уже имеющимися средствами) некий вариант, как может быть гипотетически реализован полиморфизм в условном Pascal-подобном языке, ещё не испорченном мэйнстримом, с оглядкой на Оберон. И с учётом современных, на мой взгляд наконец-то здравых, тенденций в IT. К примеру, не знаю, как правильно охарактеризовать, пусть будет как переход от понятия наследования типов к некоему теоретико-множественному подходу над ними, что ли. Прослеживается этот эффект и в Go, где "расширение" структур осуществляется через "включение" (множественное):
https://github.com/luciotato/golang-notes/blob/master/OOP.md

И ученики Вирта (если не ошибаюсь) из Scala также, наигравшись всяким разным и в огромном количестве, постепенно переползают на понятие пересечение типов (в некотором смысле эквивалентно как в Go) и их объединение -- готовится новая система типизации -- DOT-исчисление типов (dependent object types) -- в частности, см. пересечение типов в виде "A & B" и объединение как "A | B" (пока ещё в привязке с понятием наследования, с мутным явным декларированием ковариантности и контравариантности, и пр.):
https://habr.com/company/lanit/blog/334018/

(в java-инфраструктуре (всё-таки масштабная промышленная платформа) такие техники "теоретико-множественности" типов (в том числе и для ликвидации необходимости использования типов вида Any) уже обкатывались где-то то ли в Ceylon или Gosu, или же в Groovy, та ещё свалка всего возможного).


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 76 ]  На страницу 1, 2, 3, 4  След.

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


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

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


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

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