OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Вторник, 18 Июнь, 2019 23:46

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




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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Для условного Pascal-я выше существует возможность задавать виртуальные состояния через особый параметр типа, в круглых скобках (в отличие от "[...]" для параметризации полиморфизма) -- нечто вроде дискриминанта записей аля Ada, что выражает изменение состояния объекта при исполнении программы, однако статически, виртуально:
Код:
// пусть используются unit-ы

// как-то так зададим "enum":
unit State = unit Open, Closed;

// тип для работы с файлом, полиморфный и с дискриминантом типа State
unit File[T](State)
    descriptor: T;

    // в операциях указывается значение дискриминанта:

    fn open(path: string): File[T](Open);
    proc read(file: File[T](Open); out buf: array of byte; len: integer);
    proc close(var file: File[T](Open -> Closed));
end;

В runtime для дискриминанта не создаются никакие теги или флаги и т.п. Однако, может возникать потребность в реальном, уже runtime, значении состояния. Напр., при "разрыве" протокола типа. Переменная или параметр процедуры может быть "помещён" в контейнер, т.е. данные как-то сохраняются и далее переменная не используется и т.п. Для компилятора происходит "потеря" отслеживаемого объекта, в дальнейшем необходимо "восстанавливать" протокол, напр., данные извлекаются из контейнера и необходимо задать для компилятора "сохранённое" состояние. Иными словами есть потребность "плавного" перехода от виртуального состояния к реальному значению этого состояния в runtime, с "гарантиями" для компилятора, т.е. его необходимо "убедить" в валидности данных. Схема примерно такая:
Код:
// Тип-запись: дескриптор файла и состояние этого типа,
// помеченные одним регионом (компилятор контролирует корректность
// данных, т.е. их связность)
unit FileState
    fd at a: File[integer];
    fs at a: State;
end;

var st: FileState;

// открываем файл
st.fd := open(...);

// Фиксируем состояние: ".File.State" -- обращение к типу поля записи,
// с учётом типа состояния (дискриминанта):
st.fs := st.fd.File.State;

// Восстановление состояния:
case st.fd.File.State(st.fs) of
    Open: read(st.fd, ...); // тип для st.fd определён как File[integer](Open)
    Closed: ... ;
end;

Потенциальная техника typestate выше есть альтернатива протоколам в Zonnon, Composita.
В детальности нет смысла углубляться, не на каждый чих возникает потребность в подобном функционале.
В целом, методики "typestate" и "session types" имеются всякие разные, основные собраны здесь:
http://simonjf.com/2016/05/28/session-type-implementations.html

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

P.S. В статье по ссылке выше не открывается публикация насчёт реализации сессий типов в Rust. Прилагаю, если кому интересно:
Вложение:
Session Types for Rust.pdf [210.7 КБ]
Скачиваний: 37


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Суббота, 02 Февраль, 2019 02:57 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 495
PSV100 писал(а):
Однако, в любом случае возникает потребность иметь ссылку на "агрегат", следовательно, типы-"агрегаты" обречены быть только объектами в куче, создаваемыми через NEW с необходимостью получения указателя. Что влечёт соответствующие последствия (напр., организация коллекций объектов вынужденно через косвенный доступ и т.д.).

Тут весь вопрос во взаимодействии между сложностью и производителностью. Наличие записей или массивов с полями, не являющимися указателями, как общего явления, усложняет язык, но увеличивает производительность. Разбить, допустим, какую-нибудь матрицу 100х100 на 10000 указателей - и получим 10000 новых объектов в куче совершенно на ровном месте. Я смотрел на Mezzano и на A2OS. Первая ползает, вторая летает. Динамическая типизация вроде бы даёт замедление только в O(1). А вот дробление объектов на мелкие... Т.е. тут у меня нет чутья, полученного из опыта, чтобы сказать, нужно ли заморачиваться с "записями" за пределом драйверов устройств, где нужно конкретное размещение битиков в памяти. Так-то увеличение громоздкости оберона после лиспа из-за одних только пар запись-указатель на запись выглядит весьма ощутимым. С другой стороны, когда у нас есть объекты переменного размера (те же объекты в смысле ООП или массивы неизвестной длины), то мы начинаем испытывать большие трудности при попытке сложить эти объекты друг за другом. Т.е. такая низкоуровневая вещь, как фиксированность размера объекта, оказывается важной для решения о том, будет ли поле встроено в запись или вынесено за указатель. И дальше опять же копирование записи с встроенным объектом копирует и встроенный объект. Т.е. такая невинная вещь, как попытка оптимизировать программу, слепливая объекты между собой, сразу ограничивает возможные виды копирования, вторгаясь на уровень семантики. Как-то это плохо.
Цитата:
Альтернативные принципы типизации в виде понятия пересечений и объединений типов в своей основе, особо то, не сложнее понятия наследования/расширения (изложения выше в теме, конечно же, напрашиваются причесаться и в целом адекватно быть сформулированными).

Пересечения и объединения типов, а особенно при добавлении операции "не", легко выходят на комбинаторную сложность компиляции. В SBCL есть константа 4. Компилятор делает 4 прохода оптимизации и дальше оставляет код в том состоянии, до которого удалось дооптимизировать. Ввиду сложности компилятора и отсутствия диагностики очень тяжело, глядя на конкретный код, заранее предсказать, где он остановится и можно ли рассчитывать на применение той или иной оптимизации. Это - ещё хуже, чем SQL, который всё же довольно прост и показывает план запроса, а и SQL - не сахар. Это была одна из причин, по которой я сбежал от SBCL. Тут, кстати, и скорость компиляции получается не очень предсказуемой.

Причём эта константа 4 не является особенностью SBCL, а, как я понял, вообще стандартным является подход "сколько-нибудь пооптимизируем и хватит".


Последний раз редактировалось budden Суббота, 02 Февраль, 2019 03:09, всего редактировалось 2 раз(а).

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

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


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Суббота, 02 Февраль, 2019 10:30 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 495
Оказывается, что в Common Lisp и есть проекции, доступ к полям записей осуществляется только через них. У записей есть одиночное наследование. Правда, там есть проблема с неполной применимостью apply (http://clhs.lisp.se/Body/05_abe.htm)


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
budden писал(а):
Хотя, конечно, если проекции в компиляторе уже есть, то решение сделать их явными выглядит интересным. По идее-то множественное наследование в С++ чем-то на них похоже. При множественном наследовании, делая каст к предку и обратно, мы делаем примерно то же самое. Не считая того, что тут "поля записи" называются зачем-то "предками".

В случае обычного наследования в С++ возникают те отношения, которые можно определить в контексте мереологии ("состоит из ...", фактически, вне учёта понятия множеств), что и выражают проекции. При виртуальном наследовании образуются отношения таксоно́мии (отношения на множествах, иерархических, возникает классификация), выделяются общие (в итоге "разделяемые") т.н. подобъекты, и т.д.

Благодаря этой теме вспомнился проект СЛанг, и ранее было отмечено идентичность его множественного наследования понятию пересечения типов. Всё же, предлагаемое там множественное наследование есть эквивалент виртуальному (без "дублирования" объектов) с особенностями, с учётом различения объектов не только по имени, а в целом по сигнатуре (нашлась статейка с деталями по проблематике, в отличие от доступных презентаций по ссылке ранее):
Концепция наследования в современных языках программирования

А концепция unit-ов в СЛанг довольно таки неплоха в своей основе. Позволяет выражать все типы в языке, включая и базовые встроенные. В т.ч. есть возможность вводить и константные объекты или конструкторы (как true и false в лог. типе, или как константы-числа). Не хватает переменных в разрезе типа (не полей, т.е. аля static-переменные как аналог глобальных переменных "обычного" модуля, "токены" для обслуживания домена). Если бы в языке были бы ключ. слова для переменных аля привычного "var", то можно было бы задать нечто вроде "unit var v: ..." по аналогии с "class var" в той же Delphi (заодно и константные объекты вводить как "unit const ...").


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
budden писал(а):
Пересечения и объединения типов, а особенно при добавлении операции "не", легко выходят на комбинаторную сложность компиляции. В SBCL есть константа 4. Компилятор делает 4 прохода оптимизации и дальше оставляет код в том состоянии, до которого удалось дооптимизировать...

Видимо, важно, как именно интерпретировать понятия пересечения и объединения типов. В том же СЛанг, как и в С++ (имеется ввиду понятие классов), возникает некое переплетение механизмов выражения отношений между типами. В том же Go -- путь гораздо проще -- поддержка только пересечения типов (структур), с простой моделью идентификации и переопределения элементов, однако не хватает объединения типов. Объединение есть в СЛанг в виде "мультитипов" (задаваемых как "(T1|T2)"), но в простой форме, в т.ч. без поддержки "открытых" (расширяемых) вариантов.

В таком языке, как СЛанг, напрашивается разделение определения отношений между типами: пересечение типов как в Go ("расширение" или "включение" структур) и объединение типов в расширенной и гибкой форме (ака открытые и закрытые enum-ы, а также множество типов). К тому же, объединение типов есть способ ограничить "комбинаторный взрыв интерфейсов" (или объектов-типажей как в Rust), имеется ввиду существование возможности ограничить таблицы вирт. методов (в своей внутренней реализации, фактически, перейти к линейному внутреннему представлению). Пересечение типов предусматривает только статические дефиниции (все структуры и операции идентифицируются в compiletime), объединение предусматривает runtime-идентификацию типов с возможностью динамической диспетчеризации методов (скрытые теги типов могут быть ссылками/идентификаторами на таблицы вирт. методов и т.п., в целом внутренние механизмы могут быть всякие разные). Полиморфные алгоритмы допустимы в контексте общей части у всех типов в множестве (объединении) -- конформность типов, что и предлагается в СЛанг, но в ограниченной форме. Попробуем пофантазировать и расширить форму, по мотивам ранее в теме (unit-ы в СЛанг, прямо таки, аж смачные..., однако) -- попытаемся сопоставить принципы одиночного наследования/расширения и множественного пересечения и объединения типов, в некотором варианте...


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Пусть unit-ы расширяются как в Go -- пересечение типов, с множественными "инъекциями" или непосредственным встраиванием структур, с "затемнением" и переопределением элементов по потребности, структуры "собираются" в конечный вариант статически в compiletime:
Код:
// некий самостоятельный тип,
// в дальнейшем его могут включать в себя
// "любые желающие". Тип не абстрактный,
// не предусмотрено каких-то абстрактных элементов:
unit Ident
    id: integer;
    name: string;

    new fn get_ident(in a: Ident): string :=
        to_str(a.id) + ". " + a.name;
end;

// определим независимые типы геометрических фигур,
// которые "пожелали" быть расширенными типом Ident:

unit Circle extend Ident
    radius: double;

    // пожелали переопределить операцию:
    fn get_ident(in a: Circle): string :=
        "circle: " + Ident.get_ident(a);
end;

// оператор "as" используется как преобразование,
// в случаях дефиниций осуществляется приведение
// к переменным -- вводится алиас для элемента
// (вместо неких стандартных имён аля self,
// алиасов может быть множество):
unit Square as s extend Ident as i
    side: double;

    fn get_ident(in a: s): string :=
        "square: " + i.get_ident(a);
end;   

unit Rectangle extend Ident
    x, y: double;
end;


// абстрактный тип как общий функционал над фигурами:
abstract unit Shape
    abstract fn perimeter(in Shape): double;
    abstract fn area(in Shape): double;
end;

// Расширяем типы фигур абстрактным типом выше.
// Можно и наоборот -- расширять абстрактный тип аля:
//      extend unit Shape extend Circle ...

extend unit Circle extend Shape
    fn perimeter(in c: Circle): double := 2 * pi * c.radius;
    fn area(in c: Circle): double :=  pi * c.radius * c.radius;
end;

extend unit Square extend Shape
    fn perimeter(in s: Square): double := 4 * s.side;
    fn area(in s: Square): double :=  s.side * s.side;
end;

extend unit Rectangle extend Shape
    fn perimeter(in r: Rectangle): double := 2 * (r.x + r.y);
    fn area(in r: Rectangle): double :=  r.x * r.y;
end;

// расширяем тип Shape операцией, необязательной для переопределения:
extend unit Shape
    final proc shape_print(in s: Shape) :=
        println(to_str(s.perimeter()) + " " + to_str(s.area()));
end;       


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Ранее в теме была попытка представить абстрактные типы только с полиморфной параметризацией (ака конструктор класса типа). Выше абстрактные типы поближе к традиционной ООП-методологии (абстрактный unit как тип, сам по себе полиморфный), параметризация полиморфизма возможна, но уже для дополнительных свойств типа по потребности:
Код:
// абстрактный тип с тремя параметрами, для каждого указано
// требование расширения от абстрактного Numeric:
abstract unit MySpecialAbs[u, v, o with Numeric]
    ...
end;

// в Shape заменим конкретный double на параметр T,
// с требованием расширения от Numeric и MySpecialAbs:
abstract unit Shape[T with Numeric, MySpecialAbs[float, float, T]]
    abstract fn perimeter(Shape): T;
    abstract fn area(Shape): T;
end;

// или же альтернативная форма для MySpecialAbs -- именованные параметры
// ака "ассоциативные типы":
abstract unit MySpecialAbs
    // unit, который должен быть переопределён в "потомках" как синоним типа в виде:
    //     unit In1 = float;
    unit In1;
    ...
    // ... или с требованием для типа:
    unit In1 with Numeric;
    unit In2 with Numeric;
    unit Output with Numeric;
   
    // или кратко:
    unit In1, In2, Output with Numeric;
    ...
    abstract fn func1(a: MySpecialAbs; i1: In1; i2: In2): Output;
end;

// используем именованные параметры в MySpecialAbs.
abstract unit Shape[t with Numeric, MySpecialAbs[In1=float, In2=float, Output=t]] as a
    abstract fn perimeter(a): t;
    abstract fn area(a): t;
end;

// для сокращения выражений определим функцию над типами,
// зададим отдельно её сигнатуру:
unit fn MyContract(T: unit): abstract unit;
unit fn MyContract(T) := Numeric with MySpecialAbs[In1=float, In2=float, Output=T];
// или так:
unit fn MyContract(T) := Numeric with
    extend unit MySpecialAbs
        unit In1 = float;
        unit In2 = float;
        unit Output = T;
    end;

// используем функтор:
abstract unit Shape[t with MyContract(t)] as a
    abstract fn perimeter(a): t;
    abstract fn area(a): t;
end;

// пример функтора с результатом конкретного типа:
unit fn MyUnit(t with Numeric): unit := unit field1: t; field2: t end;

// применение:
var rec: MyUnit(double);


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Введём объединение типов (в множество типов элемент включается только один раз). Вместо отдельного ключ. слова аля "union" (которое слабо отличимо от unit) используется также unit, в рамках политики единого контейнера в языке, но совместно с ключ. словом of -- мем аля "unit of units" (объединение или множество типов):
Код:
// объединение некоторых беззнаковых целых типов:
unit UInts of (u64, u32, u16);
// без скобок:
unit UInts of u64, u32, u16;

// с указанием требования для элементов объединения
// (требование для типа является общим для всего объединения) --
// расширение от абстрактного Numeric:
unit SInts with Numeric of (i64, i32, i16);
// или два абстрактных типа:
unit SInts with Numeric, Repr of i64, i32, i16;
// или сложнее требование:
unit SInts with Numeric, Repr, MySpecialAbs[float, float, float] of i64, i32, i16;

// множество из двух объединений:
unit Ints of SInts, UInts;

// или из объединения и конкретных элементов:
unit Ints of SInts, u64, u32, u16;

// альтернативно как расширение объединения:
unit Ints extend SInts of u64, u32, u16;

// конечное или замкнутое объединение:
final unit FInts of SInts, UInts;

// замкнутое объединение может быть в составе
// любого иного объединения:
unit HugeInts of FInts, i128, u128;

// но существует конформность типов.
// Пусть определена процедура:
proc calc_huge(a: HugeInts) :=
    case a of
    | i128: calc_i128(a)
    | u128: calc_u128(a)
    else calc_ints(a)
    end;

// процедурный тип с открытым объединением,
// т.е. алгоритм предусматривает вариант "иначе"
// для анализа аргумента, фактически, открытое объединение
// указывает на минимально допустимую и известную границу множества:
var f: proc(a: UInts);

// тип у f совместим с calc_huge:
f := calc_huge;
var i: i64 := 434343;
f(i);

// если определить процедурный тип
// с фиксированным объединением,
// то процедура calc_huge несовместима,
// не смотря на включение в HugeInts множество FInts:
var f1: proc(a: FInts);
f1 := calc_huge; // error!

// объединения могут использоваться и для ограничений параметров полиморфизма
// (t должен быть типом, входящим в множество):
proc calc[t in HugeInts](a: t);

// параметры u и v потенциально разные элементы множества:
proc calc1[u, v in HugeInts](m: u; n: v);

Runtime-объекты типов-объединений -- переменные, параметры процедур, поля структур аля:
var h: HugeInts;

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


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Вернёмся к примеру с геом. фигурами. Пусть возникла потребность организовать гетерогенный контейнер, содержащий условно независимые типы, т.е. наши фигуры:
Код:
// определяем объединение, с требованием расширения от
// абстрактного Shape:
unit Shapes with Shape of Circle, Square, Rectangle;

// универсальный алгоритм для работы с фигурами:
procedure work(in s: Shapes);
var p, a: double;
begin
  ...
  case s of
  | Circle: p := s.perimeter()
  | Square: ...
  | Rectangle: ...
  else
        ...
  end;
  ...

  // одиночное тестирование типа
  if s is Rectangle then
    p := s.perimeter(); // здесь s как Rectangle
  end;

  // динамическая диспетчеризация вызова метода
  // (метод perimeter содержится во всех элементах объединения Shapes
  // согласно требованию расширения от Shape):
  p := s.perimeter();

  // динамическая диспетчеризация через приведение типа,
  // если тип отличен от требуемого -- trap/exception:
  a := (s as Rectangle).area();
  a := area(s as Rectangle);
end;


// определим массив для хранения фигур, как value-значения,
// фиксируем объединение (структура элементов массива конечна,
// возникает аля вариантная запись с внутренним тегом типа):
var arr: array 3 of final Shapes;

arr[0] := Circle(radius: 2.8);
arr[1] := Square(side: 5.78);
arr[2] := Rectangle(x: 10, y: 14);

for var i := 0 to 2 do
  case arr[i] of  // варианты тестирования типа конечны
  | Circle: var s: string := arr[i].get_ident();
  | Square: ...
  | Rectangle: ...
  end;

  // процедура work принимает открытое объединение,
  // типы совместимы:
  work(arr[i]);
end;


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
В СЛанг предлагается динамическая диспетчеризация по множеству аргументов (в качестве рассматриваемого объединения типов там аля ООП-классы (в рамках некоторых характеристик) и "мультитипы" как "T1|T2"), некие упрощённые "мультиметоды", или упрощённый "pattern matching" по-паскалевски, рациональное зерно имеется, тем более в случае наличия статической перегрузки операций по типам, но необходимо оценить возникающие правила перегрузки:
Вложение:
multimet.png
multimet.png [ 251.63 КБ | Просмотров: 473 ]


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Выше объединение типов было в форме множества над существующими типами (т.е. внешними по отношению к unit-объединению). Предполагается и форма с внутренними типами, "замкнутыми" (ака enum-ы):
Код:
// unit-ы объявляются внутри объединения:
final unit Option[t] of
    unit None end,
    unit Some[t] t end; // "анонимное" единственное поле

// внутренние типы не являются "самостоятельными",
// фактически, это конструкторы типа (или аля константные объекты типа в СЛанг).
// Нельзя использовать внутренние unit-ы как идентификатор типа
// в объявлении элементов, также как, например, true или false:
var n: None; // error!
var n: Option.None; // error!
var b: true; // error!

// операции для объединения:
extend unit Option[t] as a
    abstract fn repr(a): string;

    new fn is_none(a): boolean := (a is None);
    new fn is_some(a): boolean := (a is Some);
    new fn unwrap_or(opt: a; def_val: t): t :=
        case opt of
        | None: def_val
        | Some: opt; // opt теперь есть значение типа t (анонимное единственное поле в структуре)
        end;
end;

// переопределяем необходимые операции объединения
// для каждого внутреннего unit-а

extend unit Option.None as a
    fn repr(a): string := "none";
end;
extend unit Option.Some as a
    fn repr(s: a): string := to_str(s);
end;

// или альтернативная форма внутри объединения:
extend unit Option[t] as a
    abstract fn repr(a): string;
    ...

    extend unit None as n
        fn repr(n): string := "none";
    end;
    extend unit Some as s
        fn repr(i: s): string := to_str(i);
    end;
end;

var opt1: Option[integer] := None;
var opt2: Option[integer] := Some(1234);
var opt3: Option[integer] := Option.Some(5658);

var s: string := opt1.repr(); // динамическая диспетчеризация вызова метода


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Выше был примерчик закрытого объединения enum-ов. Рассмотрим вариант открытого. В этом случае возникает некая идентичность одиночному расширению записей в Оберон-ах. Для начала попробуем сделать иерархию Оберон-записей, пусть будет упрощённый "универсальный компонент" для реализации списков:
Код:
TYPE
    // Базовый тип списка. Указатель на не абстрактную пустую запись,
    // символизирует пустой список (в контексте операций тестирования типа),
    // ака традиционный Nil (в "сотрудничестве" с Cons): 
    List = POINTER TO EXTENSIBLE RECORD END;

    // Абстрактный тип непустых списков
    NonemptyList = POINTER TO ABSTRACT RECORD(List) END;

    // тип хранения данных
    Item = POINTER TO ABSTRACT RECORD END;

    // Вариант реализации списка
    LinkedList = POINTER TO RECORD(NonemptyList)
        head: Item;

        // ссылка на базовый тип. Предполагается, что tail может быть:
        // tail is List -- пустой список, tail is LinkedList -- не пустой.
        // Однако, это предположение неявное, т.е. вне контракта системы типов.
        tail: List;
    END;

// Общие операции

PROCEDURE (l: List) is_empty(): BOOLEAN, NEW, EXTENSIBLE;
BEGIN
    RETURN TRUE;
END;

PROCEDURE (l: NonemptyList) is_empty(): BOOLEAN;
BEGIN
    RETURN FALSE;
END;

// для непустых списков:
PROCEDURE (l: NonemptyList) first(): Item, NEW, ABSTRACT;
PROCEDURE (l: NonemptyList) rest(): List, NEW, ABSTRACT;

// ... определяем для LinkedList:

PROCEDURE (l: LinkedList) first(): Item;
BEGIN
    RETURN l.head;
END;

PROCEDURE (l: LinkedList) rest(): List;
BEGIN
    RETURN l.tail;
END;

Возможны разные расширения от NonemptyList для необходимых вариантов реализаций списка. В случае адаптации "компонента" для взаимодействия с иными "компонентами" требуется какое-то runtime-решение организации аля множественного наследования или интерфейсов и т.п., на чём уже акцентировалось ранее. Напр., вспомнились "фасеты":
https://forum.oberoncore.ru/viewtopic.php?f=30&t=6131#p102228


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Вариант списка в виде открытого объедения типов:
Код:
// базовый тип для списка, с внутренним абстрактным unit-ом:
unit List[T] of
    unit Nil end,
    abstract unit NonemptyList end;

// вариант реализации списка, фиксированное объединение,
// к конкретному Nil из List добавляется необходимый конкретный Cons,
// расширенный от абстрактного NonemptyList:
final unit LinkedList[T] extend List[T] of
    unit Cons extend List.NonemptyList
        head: T;
        tail: ref LinkedList[T]; // ссылка на собственный тип, в отличие от Оберон-варианта,
                                 // явная декларация состава возможных типов (в объединении)
    end;   

// необходимые операции для типов

extend unit List as a
    abstract fn is_empty(in a): boolean;
end;

extend unit List.Nil as a
    fn is_empty(in a): boolean := true;
end;

extend abstract unit List.NonemptyList as a
    fn is_empty(in a): boolean := false;

    abstract fn first(in a): T;
    abstract fn rest(in a): ref List[T];
end;

extend unit LinkedList.Cons as l
    fn first(in a: l): T := a.head;

    // результат функции переопределён на необходимый тип объединения,
    // в отличие от Оберон-версии
    fn rest(in a: l): ref LinkedList[T] := a.tail;
end;


// применение:

var l: LinkedList[integer];

l := Nil;
l := Cons(head: 10; tail: nil);
new(l.tail);
l.tail.head := 20;

// динамический вызов метода, операция is_empty определена
// для всего объединения (для всех элементов):
var b: boolean := l.is_empty();

case l of
| Nil: ...
| Cons: var i: integer := l.head
end;

// универсальный алгоритм, оперирующий базовым типом для списка:
procedure handle[t](in l: List[t]);
begin
    case l of
    | Nil: ...       
    | NonemptyList:  // тестирование на соответствие абстрактному типу
        var n: t := l.first();           // динамический вызов метода
        var ns: ref List[t] := l.rest()  // ...
    else
        ...
    end;
end;

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

Выше тип LinkedList можно было бы задать как ссылочный изначально. В СЛанг есть вариант unit-а как тип-значение, аля:
val unit Complex[T] ... end;

По аналогии возможен и ref-unit (аля POINTER TO RECORD ... END):
ref unit LinkedList[T] ... end;

В целом, видимо, в языке напрашивается некая политика интерпретации типов-значений. Пусть в случае "val unit ..." модификатор val разрешает копирование (в частности, LinkedList выше "кривой" в плане копирования значения, поскольку не копирует всю структуру возможных объектов списка). К услугам должна быть возможность перегрузки оператора аля ":=" или переопределения некоего стандартного метода как Adjust в Ада (корректировка свойств объекта после побитового копирования). Для "простых" unit-ов (без val) либо поддерживается аля move-семантика (в стиле того же Rust), или запрещается копирование (в стиле лимитированных типов в Ада). В любом случае, язык должен быть адаптирован для выбранной политики.


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

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 354
Кроме расширения типов необходимо и их "сужение".
Выше была ссылка на "фасеты", там в теме упоминается проект Lagoonа, околообероновская техника построения интерфейсов:
https://forum.oberoncore.ru/viewtopic.php?f=30&t=6131&start=40#p102346

, где рассмотрена проблематика совместимости между интерфейсами. В статейке ниже есть и примеры с подчёркнутой необходимостью сужения возможности типа:
Supporting software composition at the programming language level

Для случаев ограниченного применения типа можем его "замаскировать" (вместо создания нового типа с дублированием операций, по мотивам некоторых техник в ML-семействе).
Для типа List выше сделаем "маску" в виде иного unit:
Код:
// Модификатор "*" -- публичный (экспортируемый) доступ.

// Введём алиас для типа, он уже назначен (в иной синтаксической области установка
// алиаса недопустима, т.е. это не параметр unit-а), однако неопределённым unit --
// предварительное (упреждающее) объявление.
// Предполагается, что тип не пустой (содержит некоторую структуру данных
// и применяется в качестве операндов):
*limited unit Magic[t] = unit;

// экспортируемые доступные операции
*extend unit Magic[t]
    // операции-конструкторы
    *new proc create(out m: Magic[t]);
    *new proc create(in l: List[t]; out m: Magic[t]);

    // операция для извлечения данных
    *new proc values(in m: Magic[t]; out l: List[t]);

    *new fn is_empty(in m: Magic[t]): boolean;
end;

// Конкретное назначение алиаса типа, но не экспортируемое
unit Magic[t] = List[t];

// Расширяем тип List этим алиасом -- раскрываем, как именно
// используется тип-маска Magic.
// Оператор "as" указывает в данном случае на преобразование
// между unit-ами: unit...end as unit...end
extend unit List[t] extend Magic[t]
    proc create(out m: Magic[t]);
    proc create(in l: List[T]; out m: Magic[t]);
    proc values(in m: Magic[t]; out l: List[T]);
    fn is_empty(in m: Magic[t]): boolean;
end as unit
    proc create(out m: List[T]);
    begin
      m := Nil;
    end;
    proc create(in l: List[T]; out m: List[T]);
    begin
      m := l; // для простоты пусть будет копирование
    end;
    proc values(in m: List[t]; out l: List[T]);
    begin
      l := m;
    end;

    // данная операция одна и та же (у List и Magic),
    // реализации не требуется
    fn is_empty(in m: List[t]): boolean;

    //... или же можно определить реализацию:
    fn is_empty(in m: List[t]): boolean := m.is_empty();
end;

// или альтернативная декларация с использованием алиасов-имён:
extend unit List[t] as lst extend Magic[t] as mgc
    proc create(out mgc);
    proc create(in lst; out mgc);
    proc values(in mgc; out lst);
    fn is_empty(in mgc): boolean;
end as unit
    proc create(out m: lst);
    begin
      m := Nil;
    end;
    proc create(in l: lst; out m: lst);
    begin
      m := l;
    end;
    proc values(in m: lst; out l: lst);
    begin
      l := m;
    end;
    fn is_empty(in lst): boolean;
end;


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

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


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

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


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

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


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

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