OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Пятница, 18 Октябрь, 2019 09:12

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




Начать новую тему Ответить на тему  [ Сообщений: 76 ]  На страницу Пред.  1, 2, 3, 4  След.
Автор Сообщение
СообщениеДобавлено: Четверг, 24 Январь, 2019 19:11 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Для условного Pascal воспользуемся фишками PascalABC, которого где-то здесь на форуме рассматривали с разных сторон, в частности -- краткой формой функциональных выражений для компактности (плюс добавим альтернативу для "function" как "fn", для "procedure" как "proc"), объявлением переменных по месту и пр.

Пусть этот Pascal будет неким упрощённым Хаскеллем по-Паскалевски для бедных. Для чего для начала необходимо ввести понятие абстрактного типа (не только для record) -- полиморфный параметрический тип (обязательно), без данных, аля класс типов или "типаж" (trait в Rust):
Код:
// объявление типа:
type Show[t] = abstract
    // операция для универсального представления объекта в виде строки,
    // аргумент передаётся по ссылке, но пусть для конкретных
    // типов компилятор может копировать значение по потребности/возможности
    virtual fn str(in a: t): string;
end;

// "инстанцирование" абстрактного типа Show для конкретного integer:
open Show[integer]
    fn str(in a: integer): string := IntToStr(a);
end;

Для "инстанцирования" абстрактного типа используется ключ. слово "open" (заимствуется "открытие структур" из ML-семейства, что может быть дополняет "close" -- "закрытие" всего модуля), что есть аналог "impl" в Rust (implementation) -- в случае применения аля implementation подразумевается или ожидается далее увидеть непосредственно наличие реализации, однако не всегда она нужна:
Код:
type Show[t] = abstract
    // реализация по умолчанию для любого типа:
    virtual fn str(in a: t): string := system.represent(a);

    // Функция не виртуальная, переопределения не допускаются.
    // Полиморфный параметр-тип необязательно должен быть первым
    // в сигнатуре функции (нет "ресиверов" в качестве первого аргумента):
    fn str(maxlen: integer; in a: t): string := copy(str(a), 0, maxlen);
end;

// "открываем" тип для integer, float:
open Show[integer];
open Show[float];

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


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Абстрактные типы могут быть "вставками" (как структуры в Go) -- наследование, в т.ч и множественное:
Код:
type
  SomeAbsType[t] = abstract
    OtherAbsType1[t]; // "включаются" все операции из OtherAbsType1
    OtherAbsType2[t]; // ...
    ...
  end;

Может быть множество параметров:
Код:
type Show[u, v] = abstract
    virtual fn str(in a: u; maxlen: v): string;
end;

// Требуется предварительное введение переменных-типов,
// т.к. "открытие" осуществляется не для конкретных типов.
// Через with указываются требования "наследования" от абстрактных типов,
// в данном случае требуется соответствие безнаковому целому:
open[u; v with UInteger[v]] Show[u, v]
    fn str(in a: u; maxlen: v): string := (...);
end;

// "открытие" для конкретных типов:
open Show[i64, u32];
...

// можем сделать "перегрузку" операций
// для иных типов:
open[u; v with SInteger[v]] Show[u, v]
    fn str(in a: u; maxlen: v): string := (...);
end;
open Show[i64, i32];

// Перегрузка для иного набора параметров:
open[u, v, f with UInteger[v], AbsString[f]] Show[u, v, f]
    fn str(in a: u; maxlen: v; format: f): string := (...);
end;
...

Возникают правила перегрузки не сложнее, чем в Delphi. Подразумевается статическая диспетчеризация в compiletime (о динамике далее).
Стандартные базовые типы и операции в языке определяются через абстрактные типы (как минимум, варианты иерархии базовой математики (и прочего, как строки и т.п.) уже имеются в IT в виде классов типов в теориях и в ФЯ, в виде абстрактных типов как в Julia и т.д.).

Полиморфная параметризация возможна и в операциях:
Код:
fn func[t with Number[t]](x: t): t := (...)


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Записи (record) -- такие же, как и в Go -- с поддержкой "пересечения типов" (ссылка уже была ранее):
https://github.com/luciotato/golang-notes/blob/master/OOP.md
Код:
type
    NamedObj = record
        Name: string;
    end;

    Rectangle = record
        NamedObj; // "включение" записи
        Width, Height: float64;
    end;

proc show(in n: NamedObj) := Println(n.Name);

// Процедура для типа NamedObj доступна и в Rectangle
// из-за "включения" этого типа:
//      var r: Rectangle := (name: "Joe", Width: 10, Height: 20);
//      r.show(); // печать "Joe"
// Переопределим процедуру для Rectangle (статическая перегрузка в compiletime):
proc show(in r: Rectangle) := Println("Rectangle ", r.Name);

// Методы записей можно оформлять в open-секциях:
open NamedObj
    proc show(in n: NamedObj) := Println(n.Name);
end;

// Полиморфные записи:
type
    Complex[t with Number[t]] = record
        r, i : t
    end;

Записи могут быть и абстрактными (нет конкретных объектов такого типа. Ключевое слово "record" отличает тип от "тотально" абстрактных):
Код:
type MyRec = abstract record ... end;


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Согласно возникающей универсальности в интерпретации типов применение атрибута limited расширяется для всех типов, давая возможность организовать и встроенные лимитированные типы, как в ML (см. "приватные" типы (не в смысле прав доступа), где ключевое слово private задано справа от "=" при декларации типа):
https://caml.inria.fr/pub/docs/manual-ocaml/extn.html#sec237

Т.е. объекты могут быть созданы только через процедуры-конструкторы, но может быть преобразование к исходному типу (в т.ч. и неявное, в отличие от ML, где в целом действует механизм вывода типа с требованием явности всего и вся, что, к слову, строже (статическая типизация "пототальнее") Паскаль/Оберон-ов):
Код:
type MyInt = limited integer;

function FromInt(n: integer): MyInt;
begin
  assert (n >= 0);
  return n;
end;

Видимо, напрашивается в дополнение и какой-то более строгий механизм лимитированных типов для потребности запрета и копирования. Т.е. нечто вроде move-семантики, скорее, по мотивам как в Rust. Но это отдельная непростая тема.


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Зададим абстрактный тип и конкретизируем его для записей -- статически в compiletime:
Код:
type
    Repr[t] = abstract
        // сигнатура альтернативно может не иметь имена параметров, только типы
        virtual proc show(in t);
    end;

    NamedObj = record
        Name: string;
    end;

    Rectangle = record
        NamedObj;
        Width, Height: float64;
    end;

open Repr[NamedObj]
    proc show(in a: NamedObj) := Println(a.Name);
end;

open Repr[Rectangle]
    proc show(in a: Rectangle) := Println("Rectangle ", a.NamedObj.Name);
end;


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Теперь пусть необходима динамическая диспетчеризация методов -- аля ООП. В этом случае применяются те же абстрактные типы (возникает роль "интерфейсов" как в том же Go). Отличие в уточнении соответствия типов в параметрах абстрактного типа -- используется декларация сужения типа как ":>" -- выдвигается требование минимального содержания типа, нижняя граница для потенциального расширения -- базовый "ресивер" метода-сообщения в терминах ООП (допускается лишь единственный параметр-ресивер):
Код:
type
    // предварительное объявление типа
    NamedObj: record;

    // Тип t как минимум должен "содержать" тип NamedObj
    // или должна быть возможность "сузить" тип t до указанного:
    Repr[t :> NamedObj] = abstract
        virtual proc show(in t);
    end;

    // Реализация типов и операций ничем не отличается:

    NamedObj = record
        Name: string;
    end;

    Shape = record
        NamedObj;
        color: int32;
        isRegular: bool;
    end;

    Rectangle = record
        NamedObj;
        Shape;
        Width, Height: float64;
    end;

open Repr[NamedObj]
    proc show(in a: NamedObj) := Println(a.Name);
end;

open Repr[Shape]
    proc show(in a: NamedObj) := Println("Shape ", a.Name);
end;

open Repr[Rectangle]
    procedure show(in a: Rectangle);
    begin
        Println("Rectangle ", a.Name);
        a.Shape.show();
    end;
end;

Расширенные типы от базового (от NamedObj в примере) могут содержать перегрузки виртуальных операций абстрактного типа или нет.
Использование "интерфейсов" -- через объекты абстрактного типа:
Код:
// пусть ссылки будут в стиле "ref <type>" как в Modula-3
var arr: array 2 of (ref Repr); // массив ссылок на абстрактный тип
...
new(arr[0], Shape(Name: "n1", color: 1234, isRegular: false));
new(arr[1], Rectangle(Name: "n2", Shape(Name: "n1", color: 1234, isRegular: false),
    Width: 10, Height: 20));
...
for var i := 0 to 1 do
    arr[i].show();  // динамический виртуальный вызов метода

Можем сделать перегрузку абстрактного типа -- из статической диспетчеризации сделать динамическую:
Код:
type
    // абстрактный тип со "статикой"
    Show[t] = abstract
        virtual fn str(in a: t): string;
        fn str(maxlen: integer; in a: t): string;
    end;

// Открытие абстрактного типа с перегрузкой для "динамики".
// Указаны лишь сигнатуры операций (возможна и их реализация)
// с уточнением, где возникают аргументы-ресиверы:
open[t :> NamedObj] Show[t]
    fn str(in a: t): string;

    // здесь сгруппированы аргументы для наглядности, выделяя ресивер,
    // второй аргумент в данном случае:
    fn str(maxlen: integer)(in a: t): string;
end;

// далее возможны определения (перегрузки) типа Show
// для необходимых типов, "содержащих" NamedObj
...

Динамический виртуальный вызов метода не зависит от способа передачи аргумента: по ссылке, указателю или по значению. Т.е. свойство "динамичности" привязано к самому типу, что создаёт гипотетичные предпосылки для реализации вплоть "резиновых" стеков как в Ада, т.е. с поддержкой содержания полиморфных объектов на стеке (параметры и локальные переменные процедур) и т.д.
Компилятор может не применять динамическую диспетчеризацию, когда типы объектов точно известны.
Ни для встроенных типов, ни для записей (в т.ч. и с пересечением/включением) не допускаются операции тестирования типа (его тега) вида "is", "case of" или "with ..." и т.п.


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Возможность и необходимость тестирования типа возникают для "объединения" типов (вводится ключ. слово union):
Код:
type
    None = record end;
    Some[T] = record T end;
    Option[t] = union
        None;
        Some[T];
    end;

// или в форме объявления "по месту":
type
    Option[t] = union
        record None end;
        record Some[T] T end;
    end;

Объединение не может содержать собственных полей, это лишь декларация множества типов, каждый элемент множества (условно независимый тип, в т.ч. и когда типы-записи вводятся внутри объединения по месту) включается в объединение только один раз (но декларация включения типа может встречаться неоднократно).
Объединение возможно над всеми типами, включая и базовые встроенные:
Код:
type IntOrStr = union integer; string end;
var v: IntOrStr;

// или объявление типа по месту:
var v: union integer; string end;

// или в краткой форме (аналогично, оператор "and" выражает пересечение типов):
var v: integer or string;
...
// установка значений:
v := 10;
v := "str";
...
// перед доступом на чтение требуется идентификация типа:
case v of
    integer: println(IntToStr(v));
    string : println(v);
end;


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Как и пересечение типов, объединение может быть множественным (иерархическим). И, что важно, могут быть "открытые" (незамкнутые) объединения, допускающие своего "расширения". Проблематику последних более-менее пытаются разрулить некоторые представители ML-семейства, напр. (включая рекурсивные определения):
https://github.com/felix-lang/felix/blob/master/doc/articles/openrecursion.rst

Зададим "иерархию" объединения, внутри которого возникает понятие совместимости типа:
Код:
type
    // virtual-объединения допускают включения себя в иные объединения:
    Addable = virtual union
        record Val a: int end;
        record Add a, b: int end;
    end;

    Subable = virtual union
        Addable;  // "расширяем" Addable
        record Sub a, b: int end;
    end;

    Mulable = virtual union
        Addable;
        record Mul a, b: int end;
    end;

// функция принимает тип-объединение (через ссылку, в данном случае),
// перед использованием необходимо протестировать тип:
function eval1(in n: Addable): int;
begin
    case n of
        Val: return n.a;
        Add: return n.a + n.b;
        else return 0
    end;
end;

function eval2(in n: Subable): int;
begin
    case n of
        Sub: return n.a - n.b;
        Addable: return eval1(n):  // тестирование типа на соответствие конкретному объединению,
        else return 0              //        входящему в исходное множество типов
    end;
end;

// в "краткой" форме:
fn eval3(in n: Mulable): int :=
    case n of
        Mul: n.a * n.b;
        Addable: eval1(n);
        else 0
    end;

// объявим переменную функционального типа:
type FuncEval = function(in x: Addable): int;
var f: FuncEval;

// теперь этой переменной можно назначить любую функцию выше,
// поскольку между типами-объединениями существует совместимость
// (есть общие элементы множества)
f := eval1;
f := eval2;
f := eval3;

// или же, напр., пусть где-то в абстрактном типе определена
// виртуальная функция:
virtual fn eval(in rec: t; in expr: Addable): int;

// и где-то существуют её перегрузки для некоторых типов,
// здесь также аргументы функций являются совместимыми:
fn eval(in rec: BaseRec; in expr: Addable): int;
fn eval(in rec: ExtRec; in expr: Mulable): int;

Объединение может быть не расширяемым (конечным) и содержать виртуальные объединения:
Код:
type
    Evaluable = union
        Subable;
        Mulable;
    end;

При тестировании типа конечного объедения все варианты известны, когда как для виртуальных объединений всегда возникает потребность в "иначе"-варианте.

Выше тип Evaluable включает Subable и Mulable, при этом каждый этот тип содержит включение Addable. Однако тип Addable в итоговое множество включается только один раз (соответственно возникают лишь единственные роли Val и Add).

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


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Возможны и "анонимные" (без внутреннего идентифицирующего тега) объединения внутри записей ("вариантные" записи), когда интерпретация типа зависит от контекста:
Код:
type VarRec = record
    f1: int32;
    (f2: int32; f3: int32);
    (f4: int32);
 end;

После декларации "регулярных" полей могут быть "вариантные" (в данном случае f2 и f4 разделяют общую память). Возможны ограничения (для динамических массивов и пр.).

Фишка полезная для "системного" программирования (да и не только). Однако, может быть признана как unsafe. В крайнем случае пусть существует возможность контроля использования через директивы вида use как в представленном здесь на форуме новом Oberon/L.


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
К вопросу variadic-аргументов операций, ранее здесь в теме отмеченных. Предлагалось их интерпретация как открытых массивов. Однако по сути они ближе к записям (совокупность элементов разных типов, в т.ч. и не совместимых/сопоставимых, в отличие от массивов). Более того, скорее, необходима интерпретация именно как операндов:
Код:
// объявление функции как полиморфной (есс-но с возможностью указаний и требований):
fn format[T](s: string; f,,fs: T): string;

// что эквивалентно без параметров-типов
fn format(s: string; const f,,fs): string;

// реализация через механизм рекурсивного "инстанцирования",
// принципы обозначены ранее:
function format(s: string; const f,,fs): string;
var
    ...
    // перегруженная рекурсивная процедура для обработки variadic-аргументов
    proc fmt(const f,,fs);
    begin
        ... // использование f
        fmt(fs);
    end;
    proc fmt();
    begin
        ... // конец рекурсии
    end;
begin
  ...
  fmt(f,,fs);  // передача всех variadic-аргументов
  ...
end;
...
// использование
var s: string := format("template", [123, 33.4, 434.434]);

Variadic-аргументы могут быть в любой позиции и в любом количестве:
Код:
fn func1(const u,,us; const v,,vs; in data: MyRec): string;


Возможны и конкретные типы. И кроме модификаторов вида in и const возможна поддержка и var -- изменение передаваемых переменных:
Код:
// предполагается некая инициализация переданных переменных по ссылке
proc init(par: int; var v,,vs: int);
begin
    ...
end;

var n, m, p: int;

init(10, [n, m, p]);

// в случае применения константных выражений -- недопустимые аргументы:
init(10, [20, 2+5, x*10]);  // ошибка компиляции

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


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
И к вопросу "проекции типов" ("токены" ранее, в некоторой степени) -- распространенное понятие в теориях о типах и в функциональных языках (часто в виде функций аля ".field" или "#field" и т.п.). К слову, в той же Scala, вроде как, проекции уже хотят выкинуть из языка. Но в тех краях озабоченность иная (достичь нирваны в вопросах полиморфизма "высших порядков"), для чего их "мощности" якобы недостаточно, и есть средства с дублированием функционала. Здесь же, в условном Pascal-е, предлагается готовить их под другим соусом.
При отсутствии указателей на члены структур (записей) подобные средства не являются излишними, наоборот необходимы.

По-паскалевски проекции могут быть как стандартный тип. Пусть вместо case, как предлагалось ранее в теме, будет ключ. слово proj для задания типов-проекций в общем виде как:
type p = proj <тип-поле> of <тип-контейнер>
Код:
type MyRec = record
  a: integer;
  b: string;
end;

var
  rec: MyRec;

  // проекция конкретного поля
  p1: proj a: integer of MyRec;

  // проекция неизвестного поля
  p2: proj string of MyRec;

  // проекция на неизвестный тип
  p3: proj integer of record;
  // или в краткой форме
  p4: proj integer;
...
// использование
p1[rec] := 10; 
var i: integer := p1[rec];  // i = 10

//инициализация и использование
new(p2, MyRec.b);
p2[rec] := "str"; // rec.b = "str"

var r2: MyRec := (p1[rec], p2[rec]); // r2.a = 10, r2.b = "str"

new(p3, MyRec.a);
...

Аргументом проекции "p[...]" может быть тип-значение или указатель/ссылка на контейнер. Проекции могут быть произвольного типа (тип поля), включая и записи, функциональные типы. Возможно оперирование абстрактными типами (с динамической диспетчеризацией).

Допускается создание типа-контейнера на основе проекций, по аналогии с динамическими массивами:
Код:
var
  rec: record; // или как rec: ref record;
  p1: proj integer;
  p2: proj string;
...
new(rec, [p1, p2]);
p1[rec] := 10;
p2[rec] := "str";


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Выше в примерчиках есть некое вольное манипулирование объектами. Напр., мы создали запись:
new(rec, [p1, p2]);

Затем можем, напр. случайно, обратиться к ней через некую проекцию, которая никак не "связана" с целевым типом-контейнером -- возникает ошибка доступа к памяти:
p3[rec] := 10;

Для статического разруливания подобных ситуаций в системе типизации (чтобы поддерживать марку "тотальности") известны два решения (ну, кроме по полной программе нафаршированных систем с зависимыми типами, со своими особенностями). Первое -- как в Scala -- Path-dependent Types, см. напр.:
https://danielwestheide.com/blog/2013/0 ... types.html

Однако, здесь возникает "запаковка" типов в иные типы, что не очень-то удобно, тем более условный Pascal очень-таки "линейный" в объявлении и манипулировании типами.

Второе решение -- "регионы" как в Rust (решающие ряд задач, в т.ч. и иногда довольно-таки в мутной форме), доставшиеся ему от некоторых ML-вариациях через Cyclone (диалект С), что позволяет "разрывать" типы как угодно.
Можно воспользоваться предельно простой формой "регионов", заимствуя её у одной из вариаций Lustre (здесь на форуме есть тема про Esterel), решение для прикладников-инженеров (а не для любителей шаблонной магии С++).

Пусть регион есть некоторое виртуальное понятие связности (изначально техника возникла для решения задач управления памятью). Используется оператор "at" (или ключ. слово) при декларации элементов данных:
Код:
// Функция принимает ссылку на vector, которая помечена неким регионом "a".
// Результат функции есть новый объект типа iterator, этот объект
// также привязывается к этому же региону.
// Предварительное введение идентификатора-имени региона не требуется:
function find(in vec: vector at a; value: t): iterator at a;
begin
  ...
end;
...
// пометка полей (регион м.б. указан в любой части объявления)
type Rec = record
    a at r: ref Obj;
    b at r: ref Obj;
end;

Разметка регионами применяется и для переменных (в т.ч. привязка локальных переменных к параметрам процедур). Компилятор отслеживает корректность доступа. Побочное явление -- соблюдение совместимости области видимости, т.е. объекты должны быть в адекватном scope -- внутри параметров процедур, локализованы внутри одной записи и т.д. Без применения регионов для контроля необходимо в объектах иметь дополнительные ссылки на связанные элементы, и в runtime организовываются соответствующие проверки, и т.д.

Потребность явно применять регионы может возникать лишь для организации API, в остальном действует автоматический вывод.
Создание записи, в примере ранее, должно интерпретироваться компилятором так (предполагая, что универсальная операция new также оперирует регионами в зависимости от инициализируемых объектов):
Код:
var
  rec: ref record at r;
  p1: proj integer at r;
  p2: proj string at r;
...
new(rec, [p1, p2]);


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Типы-проекции, с поддержкой в языке определенных стандартизированных абстрактных типов для их расширения, позволяют организовать API в следующем стиле, по мотивам "STL" на "токенах":
Код:
type
    MyDomain = integer;  // "прикладной" домен для предметки

// проекция на стандартный массив (динамический, возможен и статический)
var arr: proj MyDomain of array;

new(arr);
arr.add([1, 2, 5, 5, 10]);

// индексный доступ через числовой тип как обычно в массивах:
var i: MyDomain := arr[0];  // i = 1

// индексный доступ через тип handle -- ссылка на данные:
var h: handle := arr.find(5);
var j: MyDomain := arr[h];

// сделаем проекцию с несколькими полями:
var f1: proj MyDomain of array;
var f2: proj float;  // тип для f2 и f3 ограничен -- лишь стандартные операции доступа (индексации)
var f3: proj string; // согласно базовому типу проекций

new(f1, [f2, f3]);

f1.add_rec([10, 2.3, "str"]);

var h := f1.add(20);
f2[h] := 4.5;
f3[h] := "s2";

// найти по полю f2
h := f1.find(f2, 2.3);

// найти по полям f2 и f3, игнорируя регистр в f3 (константа CaseInsensitive
// как опция для операции):
h := f1.find([f2, f3], [0, CaseInsensitive], [4.5, "s2"]);

// проекция на тип view -- организация сортировки, фильтрации и пр.
// на основе внешних данных в контейнере (данные контейнера не копируются)
var v: proj MyDomain of view;

// Привязка проекции v к f1.
// Структура, определенная для f1,
// "наследуется" и для v:
new(v, f1);

v.sort();
h := v.find(20);
var i: MyDomain := f1[h]; // или: i := v[h]
var j: float := f2[h];
var s: string := f3[h];


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Есс-но доступны разные типы-контейнеры и проекции собственных типов:
Код:
type
    Keys = record a: integer; b: string end;
    Vals = record d1: float; d2: float end;
var
    // проекция на map с составным "ключом" и "значением"
    k: proj Keys of map;
    v: proj Vals;
    h: handle;

new(k, v);

h := k.add();
k[h].a := 10;
k[h].b := "s10";
v[h].d1 := 1.2;
v[h].d2 := 4.6;

h := k.add(a: 20, b: "s20");
v[h] := (d1: 2.5, d2: 3.4);

h := k.add([30, "s1"]);
v[h] := [6.1, 4.478];
...

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

А также проекции снижают потребность в непосредственном манипулировании параметрами типов (стиль параметризации на уровне определения массивов).


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
Заключительный штрих, насчёт полиморфизма "высших порядков" -- о функциях над параметрами типов.
В целом, возможны и непосредственные лямбда-аргументы -- см. на примере той же Scala -- как "[X] => Map[K, X]":
https://habr.com/company/lanit/blog/334018/

О возможностях здесь нет смысла углубляться. Пару слов о второй форме -- о внешних функциях над типами (аля функторы в ML). Эту форму пытались применить в Go как основную для построения дженериков, но отбросили (см. статьи по ссылке ранее).

Для языка, где конструкторы типов "оторваны" от самого типа (кроме идентификатора типа как непосредственного конструктора, т.е. речь о прикладных процедурах-конструкторах, инициализирующих данные), функции типов очень даже гармоничны:
Код:
// объявление "type fn" -- функция над типами:
type fn my_rec(type t) := record a: t; b: t end;

// или так (без "type" для аргументов):
type fn my_rec(t) := record a: t; b: t end;

var
    r1: my_rec(integer); // объявление переменной с типом, полученным от функции
    r2: my_rec integer;  // или так

// для аргументов применяются типы и константы:
type fn my_arr(type item with Numeric[item]; const s: Integer) :=
    // в данном случае тестирование на соответствие абстрактному типу:
    case item of 
        Integer: array s of int64;
        Float: array s of float64;
    end;

var arr: my_arr(int32, 10);


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 371
С помощью функций типов реализуются "контракты" как в Go для компактификации объявлений и повторного использования (однако принципы организации ограничений/требований различен, что очевидно):
Код:
// Пусть необходимо сократить выражение требований для "открытия" типа,
// на примере ранее:
open[u, v, f with UInteger[v], AbsString[f]] Show[u, v, f]
...
end;

// можно убрать параметры после указания "Show",
// эти параметры "передаются" в этот конструктор типа
// автоматически (такая интерпретация, в некоторой степени,
// интуитивно понятна при наличии в языке механизма каррирования функций):
open[u, v, f with UInteger[v], AbsString[f]] Show
...
end;

// определим функтор абстрактного типа
type fn my_contract(t1, t2) :=
    abstract
        UInteger[t1];
        AbsString[t2];
    end;

// используем функтор в качестве требования
open[u, v, f with my_contract(v, f)] Show
...
end; 


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

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

Буду признателен, если у кого-то имеются свои взгляды на проблематику. Возможно, вдруг, всё-таки, где-то имеются и конкретные планы непосредственно по Оберон-ам, пока мало публично известные (к тому же, здесь в последнее время представлены и Oberon/L, и даже некий Oberon 3, а вось есть планы ...).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 25 Январь, 2019 03:36 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 530
Лично мне мешают по сути отреагировать ряд вещей:

1. В виде потока сознания читать трудно. Нужно как-то больше в форме научной работы: т.е. аннотация, автореферат и системное изложение содержания

2. Моё безкультурье - я плохо знаю ML-образные языки. Т.е., чтобы я понял, нужны ссылки на какие-то короткие объяснялки сути заимствованных концепций. Например, я не понимаю, зачем нужны проекции. Если это аналог указателя на элемент записи, то я бы из минимализма постарался бы от этого отказаться. Потому что возникает проблема времени жизни, и чтобы её решить, нужно нагородить ещё костылей. Т.е. тут нужно принять за аксиому, что мы жертвуем частью производительности ради простоты.

3. Я сейчас занимаюсь BlueBottle. Вещь очень вдохновляющая, но я боюсь, что ближайший год, если я вообще всё это не брошу, буду заниматься вопросами оконного менеджера, кириллизации, сочетаний клавиш, перехода к определению и автодополнения. Т.е. я хочу иметь скриптовый язык для себя, но я отдаляюсь от него всё больше с каждым годом, погружаясь в пучину всё менее развитых технологий.

4. Дефицит ресурсов.

5. Собственные нереализованные замыслы.

Единственное, чтобы сверить часы, я отмечу вот что:

  • И ББЦБ, и А2 - полноценные системы и в них есть все опасные конструкции, как и в том же расте. И висячие указатели, и void *, и юнионы. Всё это есть, просто оно слегка припрятано. Нужно посмотреть, как они используются. Так можно получить опыт.
  • Принцип KISS, положенный в основу оберонов, требует от меня серьёзной переоценки того, как я вижу мир. Впрочем, лисп тоже во многом построен так, поэтому революция не слишком большая.
  • Тип как аргумент в ББЦБ есть, правда, только во встроенных функциях.
  • Я против private. Вместо запрета нужны мягкие рекомендации плюс анализ их соблюдения. Всё равно программист всегда найдёт способ взломать любую систему private-ов. В Паскале и С++ я лично это делал без большого труда. В ББЦБ с помощью ADR и какой-то ещё подобной фигни можно делать совершенно ужасные вещи, например, писать по любому адресу.
  • У меня пока вообще нет идей о том, как должны выглядеть типизированные контейнеры. После type erasure контейнеры в Java и С# похожи на лисповые, где вообще хранится всегда Object. Не факт, что накладные расходы (в плане усложнения прикладного кода) на типизацию, на все эти ковариантности и многоэтажную шаблонность действительно окупаются увеличением безопасности. Если отказаться от типизации контейнеров, или ограничиться небольшим количеством Ad Hoc случаев, то многие сложные вещи резко схлопываются и исчезают.
  • Я сомневаюсь в целесообразности лямбд. Они нарушают инкапсуляцию, затрудняют отладку, плохо поддаются интроспекции. Хотя на практике во многих случаях они ну просто незаменимы. Может быть, нужны какие-то ограниченные варианты лямбд. Дельфовая procedure of object - один из возможных вариантов.
  • Я пока не осознал, насколько хорош подход к рилтайму и к параллельности в A2. А это вопрос критично важный. Полноценная ОС должна поддерживать рилтайм, иначе не будет ни кина, ни танцев. Т.е. даже паршивая винда поддерживает рилтайм. Если А2 недостаточна в этом плане, то ей место на помойке. Пока я не понимаю, не является ли хвалёный рилтаймовый сборщик мусора рекламным трюком. Если бы он был дельной вещью, его бы давно спёрли. Но мне очень умные люди говорили, что RT Java так и не сложилась. Это ЖЖЖ неспроста. И этот вопрос ключевой, он гораздо важнее многих других.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 25 Январь, 2019 04:05 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 530
P.S. А вообще в оберонах интересно было бы переоберонить Вирта, т.е. ещё упростить оберон. Насчёт собственно оберонов я не в курсе, а в КП явно упрощение не доведено до конца:

- и модули, и объекты являются единицей инкапсуляции.
Т.е. одно понятие инкапсуляции продублировано в двух
схожих, но различных явлениях в языке. Это значит,
что простота недополирована.
- модули заодно являются единицей инкапсуляции и единицей сборки. Неортогонально, а значит, простота и тут недополирована.
- есть два неправильных понятия подтипа. Подтипы в числах неправильные из-за потери точности: целое не является подтипом плавающего, хотя оно таковым в оберонах выглядит. Также и наследник не является подтипом предка, как учит нас лискова. Вместо двух этих неправильных понятий подтипа хватило бы одного правильного.
Хотя может быть, наследование реализации (запись является под-записью) является отдельным явлением, ортогональным к понятию подтипа (и вроде в Go это именно так и есть).
- есть параметрические типы, эти вот самые Array<T>,
и есть даже аналог ковариантности - совместимость массивов. Но параметричность прибита гвоздями именно к массивам. Соответственно, сложность в компиляторе есть
и за неё уплачена полная цена, а выгода от сложности
получается очень ограниченная. Это тоже косяк.
- есть некоторые встроенные функции, принимающие параметр любого типа, но этот механизм недоступен пользователю. В итоге в компиляторе куча ифов, а пользователю фиг. А можно было бы механизм полиморфизма сделать примитивом (хотя бы тем же моим ITEM-ом, но скорее новой версией, до которой руки не дошли), тогда компилятор стал бы намного компактнее, ифы бы исчезли, а многие примитивы, зашитые в компилятор, можно было бы вынести и элегантно реализовать отдельными кусочками на базе примитивов. Опять же простота недополирована, мощь недораскрыта.
- есть union типы как в лиспе, например, в КП в компиляторе существует тип всех целых чисел, и компилятор что-то там об этом может рассуждать. Но пользователю опять же фиг. Сделать union тип first-class понятием, ограничив его так, чтобы оно осталось безопасным.

Вот придумать бы такой идеализированный оберон, в котором всё ортогонально, без дублирования и в котором реализована линейная оболочка ортогональных векторов, а не жалкий огрызок оной.
Может быть этого и не выйдет, но задачка выглядит занимательной хотя бы с точки зрения эстетства.
Всё это я замышлял сделать на базе ББЦБ, теперь всё ещё мечтаю сделать на базе А2, но тут, конечно, уже руки могут и не дойти.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 25 Январь, 2019 05:47 

Зарегистрирован: Вторник, 26 Январь, 2010 09:31
Сообщения: 714
Откуда: Барнаул
budden, рекомендую сначала прочитать и осмыслить report от Вирта "От Модулы к Оберону"


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

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


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

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


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

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