Информация об изменениях

Сообщение Re: стоит создать ЯП? от 11.10.2023 1:47

Изменено 11.10.2023 2:07 Sm0ke

Re: стоит создать ЯП?
Здравствуйте, Shmj, Вы писали:

S>Вот если посмотреть популярные ЯП — то это дофига работы команд. Годы работы, годы доработок. Т.е. идея может быть вдохновлена одним человеком (на самом деле он эту идею где-то подсмотрел, как правило), а вот разработка языка — это уже только команда.


S>Получается хороший новый язык не может создать один человек и это всегда годы работы. Т.е. неожиданно новый ЯП возникнуть не может, в отличие от JS-фреймворка типа Vue.


S>Но бывают и исключения. Вот же наши как-то смогли создать тот же Nemerle вообще без особых затрат, силами энтузиастов. Как так?



В одиночку разработать свой ЯП я считаю возможным. Но трудности возникают когда приходится принять то или иное решение.
Это может быть и касательно синтаксиса, и касательно реализации, или установки неких ограничений.
Совместными усилиями, как мне кажется, результата можно добиться быстрее.

PHP изначально сделал Расмус. Классов там не было, их добавили позднее (в 5-ой версии), когда появилась команда разработчиков. Енумы там появились вообще относительно недавно (версия 8.1). Кстати имена переменных в PHP начинаются с символа $
Квадратные скобки там служат не только для обращения к элементу по индексу, но и для создания массива. Все массивы в PHP ассоциативны (как ключ допустимо и целое число и строка текста).

Я думаю многие задумывались поменять что-то в уже существующих ЯП, с которыми они работали, или что-то туда добавить. А не лучше ли вообще сделать своё? — Примерно такая идея у меня возникла ещё когда я учился в Колледже. Вам же это знакомо?

Я действительно хочу учавствовать в разработке нового ЯП!

Си++ хорош, но слишком переусложнён. Его стандартники делают с ним, то что хотят они — и это не всегда чего хотим мы.
Когда методы определяются внутри самого класса, то класс постепенно разрастается и превращается в монстра великана.
Мне кажется более наглядным, когда в программе данные определяются отдельно от действий над ними.

Я предлагаю создать свой язык вместе. У меня есть идеи, чтобы их обсудить.

Фичи языка:

*- Ячейка — это может быть: локальная переменная, параметр функции, свойство модуля, и т.д.
*- Возможность работать с типом, как со значением. Тип такой ячейки будет $type
*- Имена системных типов начинаются к примеру с символа $ (но это можно указать в конфиге, как и другие начальные символы).

Таким образом добавление системных типов не будет конфликтовать с уже написанным пользовательским кодом.
Тип может быть ключом ассоциативного массива, ведь его можно сравнивать с другим типом как просто значение.

t1 = $int
t2 = $float
t1 <> t2

*- Императивный кусок кода помещается в фигурные скобки и имеет тип $seq (sequence).
do = {
  /* expression-list here */
}


Значение типа $seq можно передавать в функцию, присваивать в ячейку, и даже использовать как ключ в ассоциативном массиве.
Это позволит определять языковые конструкции как обычные функции.
Сами по себе фигурные скобки — действий внутри не выполняют без определённой команды. Это называется вычисление по требованию.

* По сути sequence — это как лямбда без параметров.
do = {
  local_var = { call_some_fn() }
  local_var()

  call_other_fn(local_var)
}


Приблизительный пример:
if_each!
[
  { a == b } : { /* действие 1 */ }
  { /* условие 2 */ } : { /* действие 2 */ }
]

if_first!
[
  { a == b } : { /* действие 1 */ }
  { /* условие 2 */ } : { /* действие 2 */ }
  $bool.true : { /* действие иначе */ }
]

Квадратные скобки — массив/мапа. Двоеточие — разделитель в паре ключ-значение.
hint: думаю нужно отделить типы $map от $array

Символ восклицательный знак после ячейки с функцией — применить её к последующему выражению (просто чтобы не писать круглые скобки if_first([])).
if_first — выполняет sequence каждого условия до тех пор, пока не встретит первое правдивое, и тогда выполнит sequence действие из значения пары.
if_each — будет проверять все условия, даже после того как встретит правдивое.

Такая штука ведь нагляднее, чем городить огород из if else if else ?

Функцию вывода на консоль или в файл можно сделать чтобы она принимала sequence. Так она итератором будет вычислять элементы $seq — выражения, и выводить их результат, пока не они не закончатся, или не возникнет ощибка вывода / исключение, при которой оставшиеся выражения не будут вычислены.

@main

do = {
  /* c-style function call */
  @std.write( {sinus(a), " ", cosine(b)} )

  /* Smarty style function call */
  {sin(a), " ", cos(b)} |write@std
}

Собачка — начало имени модуля. Файл программы начинается с указания модуля. Далее следуют ячейки модуля — его свойства.
Это декларативная часть. Императивная часть находится внутри фигурных скобок.
При запуске программы выполнится sequence из ячеки do модуля @main

Запятая — разделитель опциональный.

Вертикальная палка перед ячейкой с функцией — применить функцию как метод к результату предшествующего выражения.
Так можно вызывать функции по цепочке слева-направо. Эта идея подсмотрена в PHP библиотеке шаблонов Smarty
a |sinus |write@std(to: file)
/* аналог: */
write@std(sinus(a), to: file)

-- line-comment

a |sinus => x |write_line@std(to: file)
write_line@std( x = sinus(a), to: file )

Оператор "присвоить вправо" => в общем то под вопросом.

Определение структур как типов: команда #struct
Команда #fn определяет функцию
@lib

coords = #struct ( x y )

scale = #fn (pos, factor) {
  #ret = coords(pos.x * factor, pos.y * factor)
}

do = {
  pos = coords(10 20) |scale(5.5)
}

Ячейка результата вызова функции #ret
Если добавить в язык Возможность её-же принять как параметр функции, то:
scale = #fn (#ret, factor) {
  #ret = coords(pos.x * factor, pos.y * factor)
}

do = {
  pos_1 = coords(10, 20)
  pos_2 = pos_1 |scale(2)
  pos_2& |scale(0.5)
}

Строчка: pos_2& |scale(0.5) передать яцейку pos_2 как link на ячейку (функция сможет её изменить)
Строчка: pos_1 |scale(2) запись в ячейку #ret в этом случае не изменит ячейку pos_1

scale можно переписать по другому:
scale = #fn (#ret, factor) {
  (#ret.x, #ret.y) *= factor
}

do = {
  pos_1 = coords(10, 20)
  pos_1 |scale(2)
}

Тут будут изменены свойства (x y) у координат в pos_1
Ведь структуры передаются по указателю, как и прочие значения системной категории _compound
Все категории типов (значение $cat) произрастают из базовой _any, включённой во все типы по-умолчанию.
Категория — это просто категория, она не хранит в себе данных. Каждый тип может принадлежать своему набору категорий.
В программе можно проверить их принадлежность. Это может быть полезно при динамической типизации.
При строгой типизации $cat можно использовать как ограничение (например ограничить тип параметра функции).
@sample

squared = #fn(_plain_number: n) { #ret = n*n }

do = {
  i = 2 |squared
  f = 1.5 |squared
}

@global

/* exposition only */

#cat (
  _any /* base category */

  _compound /* values of _compound types are passed by pointer */

  _plain /* values of _plain types are passed by-value */
  _number

  _plain_number #includes( _plain _number )
)

_plain_number #refers_to ($int $float)

@lib

complex = #struct( re im )
complex #refers( _number )

Категории можно использовать как обычные значения в программе, их сравнивать, использовать как ключ в $map.
Ведь тип $cat пренадлежит категории _map_key

Естественно пользователь сможет определять и свои категории типов.
( _null / _undefined ) — пример для пустых значений при динамической типизации, возможно это системные категории.

Атрибуты

Например у всех типов есть атрибут initial — это начальное значение ячейки, которое будет использовано при неуказании инициализации.
Доступ к атрибутам происходит через оператор стрелка ->
range = #struct [$type: value_type] ( value_type: from, to )

range->initial(from: 0, to: value_type->max)
/* или */
range->initial.from = value_type->zero
range->initial.to = value_type->max

Что это? Да это же шаблон структуры. Параметр шаблона под именем value_type — тип.
А могло быть и число
#struct [$type: Type, $int: N] ()
А мог быть и тип с ограничением $type_refers[_number]
А могла быть и категория с ограничением $cat_includes[_plain]

Собственно нужны ли категории и шаблоны в языке — под вопросом.

Про типизацию и название языка
Мне нравится название ksi-lang (и строгая и динамическая типизация)
ksi-script (только динамическая типизация)
ksi-pl (только строгая типизация)

Я долгое время пилю интерпретатор для ksi-script
Дело в том, что я по разным причинам откладывал разработку и возвращался к ней снова.
В одиночку непросто это.

Назначение языка — для любых целей. Хотя кси скрипт скорее всего для web, и как скриптовый движок в game-dev

p/s
Вопрос: синглтон или набор статик свойств
Ответ: вид значений #nest (это команда определиния типа)
colors = #nest (
  red = 0h_ff0000
  black = 0
)

/* colors.red |write */

Попытка создания значения типа, который nest only, приводит к получению этого же типа.

К струткуре можно добавить статик свойства командой #add_nest
@sample

color = #struct ( r g b a )
color->initial.a = 255

color #add_nest (
  from_code = #fn($int: code) { /* вычисления */ }

  red_color = color.from_code(colors.red)
)


Перечисления — они для чёткого набора состояний ячейки.
У элемента перечисления я думаю можно добавить атрибут data, чтобы там что-то хранить.
Атрибут data для каждого элемента перечисления привязан к этому элементу. Сами элементы передаются по значению-номеру (их data не копируется при этом).
align_h = #enum (left center right)
align_h.left->data = /* да тут что угодно, хоть даже структура */

do = {
  a = align_h.center
  a->data = /* what? can we change it? */
  a->name |write_line@std
  /* align_h.center->name */
}

Можно сделать атрибуты ->name ->index ->next_wrap (обсуждаемо)
А сомому енуму атрибут align_h->elements
align_h->elements |each: #fn(it) { it->data |write }

Вот такую примерно можно придумать интроспекцию.

В нюансах все прелести и сложности. Как делать захват локальных ячеек в sequence, который внутри sequence ? Всё ли захватывать, или указывать конкретные, или автоматом по необходимости (как в javascript).

Нужна ли языку константность (read_only ячейки)
$int: (c# = 5, x = 4, y# = c + x)
c += 1 /* ошибка */

Т.е при первом присвоении в имени ячейки окончание — спецсимвол из конфига.
В использовании имени в дальнейшем ридонли символ уже не указывается (чтобы не пришлось переписывать все места вручную, привет ide).

private public protected / friend — отдельная тема. Но я бы не тянул геттеры и сеттеры из си шарп

Наследование — сомнительно, но можно сделать команду #insert_struct
coords = #struct( x y )
game_object = #struct (
  name
  #insert_struct( coords )
  type
)

Это позволит при интроспекции обходить свойства структуры именно в заданной последовательности. (В отличии от наследования, где сперва идёт база)

Доступ к под-объекту — как вариант в индексе [] тип
rabbit = game_object( coords:(1 1), name: "Vitalik" )
pos = rabbit[coords]


Ещё есть идея
std::optional аналог

#cat ( _yes )

range = #options (
  _any:
  from
  to
  
  $cat_exact[_yes]:
  exact_from
  exact_to
)
r = range(to: 5, exact_to: _yes)

Оно хранит только заданные опции. Чтение из незаданных — вопрос. (или _null / _absent; либо ошибка/exception)
В отличии от $map — имена допустимых ключей явно заданы в определении.
Способ хранения данных — implementation defined
(наверно для $cat_exact можно по битам опции разложить, или придумать свой #bit_set / #flags)

Для системных функций я бы ещё сделал префикс &
&if_first &each &loop &write &switch &catch &throw
Так мы освобождаем имя модуля @std для прочих штук.

--
Для исключений (если да) — атрибуты:
e->source.line
e->source.column
e->source.file

Получится собрать команду разработчиков нового языка программирования в этом году?
Какие идеи вам приглянулись?

pp/s Хотел сперва вкратце написать про ksi, чтож
Даже идею про #actions (аналог интерфейсам не показал)

do = {
  c = 0h_e3d7f5 !as[color]
}

color = #struct ($integer_unsigned[8]: r g b)

color #allow_actions( conversion_from[_integer] )

conversion_from = #actions [$hint: from_type] (
  as = #fn_params [$type: to_type] (from_type: this) : to_type
)

conversion_from[$int].as[color] = #fn (_integer: this) : color {
  [#ret.r #ret.g #ret.b] = (this !make_byte_array: [3 2 1])
}

Ещё можно придумать команду #allow_actions_only, исключающую применение повторябельной #allow_actions
Re: стоит создать ЯП?
Здравствуйте, Shmj, Вы писали:

S>Вот если посмотреть популярные ЯП — то это дофига работы команд. Годы работы, годы доработок. Т.е. идея может быть вдохновлена одним человеком (на самом деле он эту идею где-то подсмотрел, как правило), а вот разработка языка — это уже только команда.


S>Получается хороший новый язык не может создать один человек и это всегда годы работы. Т.е. неожиданно новый ЯП возникнуть не может, в отличие от JS-фреймворка типа Vue.


S>Но бывают и исключения. Вот же наши как-то смогли создать тот же Nemerle вообще без особых затрат, силами энтузиастов. Как так?



В одиночку разработать свой ЯП я считаю возможным. Но трудности возникают когда приходится принять то или иное решение.
Это может быть и касательно синтаксиса, и касательно реализации, или установки неких ограничений.
Совместными усилиями, как мне кажется, результата можно добиться быстрее.

PHP изначально сделал Расмус. Классов там не было, их добавили позднее (в 5-ой версии), когда появилась команда разработчиков. Енумы там появились вообще относительно недавно (версия 8.1). Кстати имена переменных в PHP начинаются с символа $
Квадратные скобки там служат не только для обращения к элементу по индексу, но и для создания массива. Все массивы в PHP ассоциативны (как ключ допустимо и целое число и строка текста).

Я думаю многие задумывались поменять что-то в уже существующих ЯП, с которыми они работали, или что-то туда добавить. А не лучше ли вообще сделать своё? — Примерно такая идея у меня возникла ещё когда я учился в Колледже. Вам же это знакомо?

Я действительно хочу учавствовать в разработке нового ЯП!

Си++ хорош, но слишком переусложнён. Его стандартники делают с ним, то что хотят они — и это не всегда чего хотим мы.
Когда методы определяются внутри самого класса, то класс постепенно разрастается и превращается в монстра великана.
Мне кажется более наглядным, когда в программе данные определяются отдельно от действий над ними.

Я предлагаю создать свой язык вместе. У меня есть идеи, чтобы их обсудить.

Фичи языка:

*- Ячейка — это может быть: локальная переменная, параметр функции, свойство модуля, и т.д.
*- Возможность работать с типом, как со значением. Тип такой ячейки будет $type
*- Имена системных типов начинаются к примеру с символа $ (но это можно указать в конфиге, как и другие начальные символы).

Таким образом добавление системных типов не будет конфликтовать с уже написанным пользовательским кодом.
Тип может быть ключом ассоциативного массива, ведь его можно сравнивать с другим типом как просто значение.

t1 = $int
t2 = $float
t1 <> t2

*- Императивный кусок кода помещается в фигурные скобки и имеет тип $seq (sequence).
do = {
  /* expression-list here */
}


Значение типа $seq можно передавать в функцию, присваивать в ячейку, и даже использовать как ключ в ассоциативном массиве.
Это позволит определять языковые конструкции как обычные функции.
Сами по себе фигурные скобки — действий внутри не выполняют без определённой команды. Это называется вычисление по требованию.

* По сути sequence — это как лямбда без параметров.
do = {
  local_var = { call_some_fn() }
  local_var()

  call_other_fn(local_var)
}


Приблизительный пример:
if_each!
[
  { a == b } : { /* действие 1 */ }
  { /* условие 2 */ } : { /* действие 2 */ }
]

if_first!
[
  { a == b } : { /* действие 1 */ }
  { /* условие 2 */ } : { /* действие 2 */ }
  $bool.true : { /* действие иначе */ }
]

Квадратные скобки — массив/мапа. Двоеточие — разделитель в паре ключ-значение.
hint: думаю нужно отделить типы $map от $array

Символ восклицательный знак после ячейки с функцией — применить её к последующему выражению (просто чтобы не писать круглые скобки if_first([])).
if_first — выполняет sequence каждого условия до тех пор, пока не встретит первое правдивое, и тогда выполнит sequence действие из значения пары.
if_each — будет проверять все условия, даже после того как встретит правдивое.

Такая штука ведь нагляднее, чем городить огород из if else if else ?

Функцию вывода на консоль или в файл можно сделать чтобы она принимала sequence. Так она итератором будет вычислять элементы $seq — выражения, и выводить их результат, пока не они не закончатся, или не возникнет ощибка вывода / исключение, при которой оставшиеся выражения не будут вычислены.

@main

do = {
  /* c-style function call */
  @std.write( {sinus(a), " ", cosine(b)} )

  /* Smarty style function call */
  {sin(a), " ", cos(b)} |write@std
}

Собачка — начало имени модуля. Файл программы начинается с указания модуля. Далее следуют ячейки модуля — его свойства.
Это декларативная часть. Императивная часть находится внутри фигурных скобок.
При запуске программы выполнится sequence из ячеки do модуля @main

Запятая — разделитель опциональный.

Вертикальная палка перед ячейкой с функцией — применить функцию как метод к результату предшествующего выражения.
Так можно вызывать функции по цепочке слева-направо. Эта идея подсмотрена в PHP библиотеке шаблонов Smarty
a |sinus |write@std(to: file)
/* аналог: */
write@std(sinus(a), to: file)

-- line-comment

a |sinus => x |write_line@std(to: file)
write_line@std( x = sinus(a), to: file )

Оператор "присвоить вправо" => в общем то под вопросом.

Определение структур как типов: команда #struct
Команда #fn определяет функцию
@lib

coords = #struct ( x y )

scale = #fn (pos, factor) {
  #ret = coords(pos.x * factor, pos.y * factor)
}

do = {
  pos = coords(10 20) |scale(5.5)
}

Ячейка результата вызова функции #ret
Если добавить в язык Возможность её-же принять как параметр функции, то:
scale = #fn (#ret, factor) {
  #ret = coords(pos.x * factor, pos.y * factor)
}

do = {
  pos_1 = coords(10, 20)
  pos_2 = pos_1 |scale(2)
  pos_2& |scale(0.5)
}

Строчка: pos_2& |scale(0.5) передать яцейку pos_2 как link на ячейку (функция сможет её изменить)
Строчка: pos_1 |scale(2) запись в ячейку #ret в этом случае не изменит ячейку pos_1

scale можно переписать по другому:
scale = #fn (#ret, factor) {
  (#ret.x, #ret.y) *= factor
}

do = {
  pos_1 = coords(10, 20)
  pos_1 |scale(2)
}

Тут будут изменены свойства (x y) у координат в pos_1
Ведь структуры передаются по указателю, как и прочие значения системной категории _compound
Все категории типов (значение $cat) произрастают из базовой _any, включённой во все типы по-умолчанию.
Категория — это просто категория, она не хранит в себе данных. Каждый тип может принадлежать своему набору категорий.
В программе можно проверить их принадлежность. Это может быть полезно при динамической типизации.
При строгой типизации $cat можно использовать как ограничение (например ограничить тип параметра функции).
@sample

squared = #fn(_plain_number: n) { #ret = n*n }

do = {
  i = 2 |squared
  f = 1.5 |squared
}

@global

/* exposition only */

#cat (
  _any /* base category */

  _compound /* values of _compound types are passed by pointer */

  _plain /* values of _plain types are passed by-value */
  _number

  _plain_number #includes( _plain _number )
)

_plain_number #refers_to ($int $float)

@lib

complex = #struct( re im )
complex #refers( _number )

Категории можно использовать как обычные значения в программе, их сравнивать, использовать как ключ в $map.
Ведь тип $cat пренадлежит категории _map_key

Естественно пользователь сможет определять и свои категории типов.
( _null / _undefined ) — пример для пустых значений при динамической типизации, возможно это системные категории.

Атрибуты

Например у всех типов есть атрибут initial — это начальное значение ячейки, которое будет использовано при неуказании инициализации.
Доступ к атрибутам происходит через оператор стрелка ->
range = #struct [$type: value_type] ( value_type: from, to )

range->initial(from: 0, to: value_type->max)
/* или */
range->initial.from = value_type->zero
range->initial.to = value_type->max

Что это? Да это же шаблон структуры. Параметр шаблона под именем value_type — тип.
А могло быть и число
#struct [$type: Type, $int: N] ()
А мог быть и тип с ограничением $type_refers[_number]
А могла быть и категория с ограничением $cat_includes[_plain]

Собственно нужны ли категории и шаблоны в языке — под вопросом.

Про типизацию и название языка
Мне нравится название ksi-lang (и строгая и динамическая типизация)
ksi-script (только динамическая типизация)
ksi-pl (только строгая типизация)

Я долгое время пилю интерпретатор для ksi-script
Дело в том, что я по разным причинам откладывал разработку и возвращался к ней снова.
В одиночку непросто это.

Назначение языка — для любых целей. Хотя кси скрипт скорее всего для web, и как скриптовый движок в game-dev

p/s
Вопрос: синглтон или набор статик свойств
Ответ: вид значений #nest (это команда определиния типа)
colors = #nest (
  red = 0h_ff0000
  black = 0
)

/* colors.red |write */

Попытка создания значения типа, который nest only, приводит к получению этого же типа.

К струткуре можно добавить статик свойства командой #add_nest
@sample

color = #struct ( r g b a )
color->initial.a = 255

color #add_nest (
  from_code = #fn($int: code) { /* вычисления */ }

  red_color = color.from_code(colors.red)
)


Перечисления — они для чёткого набора состояний ячейки.
У элемента перечисления я думаю можно добавить атрибут data, чтобы там что-то хранить.
Атрибут data для каждого элемента перечисления привязан к этому элементу. Сами элементы передаются по значению-номеру (их data не копируется при этом).
align_h = #enum (left center right)
align_h.left->data = /* да тут что угодно, хоть даже структура */

do = {
  a = align_h.center
  a->data = /* what? can we change it? */
  a->name |write_line@std
  /* align_h.center->name */
}

Можно сделать атрибуты ->name ->index ->next_wrap (обсуждаемо)
А сомому енуму атрибут align_h->elements
align_h->elements |each: #fn(it) { it->data |write }

Вот такую примерно можно придумать интроспекцию.

В нюансах все прелести и сложности. Как делать захват локальных ячеек в sequence, который внутри sequence ? Всё ли захватывать, или указывать конкретные, или автоматом по необходимости (как в javascript).

Нужна ли языку константность (read_only ячейки)
$int: (c# = 5, x = 4, y# = c + x)
c += 1 /* ошибка */

Т.е при первом присвоении в имени ячейки окончание — спецсимвол из конфига.
В использовании имени в дальнейшем ридонли символ уже не указывается (чтобы не пришлось переписывать все места вручную, привет ide).

private public protected / friend — отдельная тема. Но я бы не тянул геттеры и сеттеры из си шарп

Наследование — сомнительно, но можно сделать команду #insert_struct
coords = #struct( x y )
game_object = #struct (
  name
  #insert_struct( coords )
  type
)

Это позволит при интроспекции обходить свойства структуры именно в заданной последовательности. (В отличии от наследования, где сперва идёт база)

Доступ к под-объекту — как вариант в индексе [] тип
rabbit = game_object( coords:(1 1), name: "Vitalik" )
pos = rabbit[coords]


Ещё есть идея
std::optional аналог

#cat ( _yes )

range = #options (
  _any:
  from
  to
  
  $cat_exact[_yes]:
  exact_from
  exact_to
)
r = range(to: 5, exact_to: _yes)

Оно хранит только заданные опции. Чтение из незаданных — вопрос. (или _null / _absent; либо ошибка/exception)
В отличии от $map — имена допустимых ключей явно заданы в определении.
Способ хранения данных — implementation defined
(наверно для $cat_exact можно по битам опции разложить, или придумать свой #bit_set / #flags)

Для системных функций я бы ещё сделал префикс &
&if_first &each &loop &write &switch &catch &throw
Так мы освобождаем имя модуля @std для прочих штук.

--
Для исключений (если да) — атрибуты:
e->source.line
e->source.column
e->source.file

Получится собрать команду разработчиков нового языка программирования в этом году?
Какие идеи вам приглянулись?

pp/s Хотел сперва вкратце написать про ksi, чтож
Даже идею про #actions (аналог интерфейсам не показал)

do = {
  c = 0h_e3d7f5 !as[color]
}

color = #struct ($integer_unsigned[8]: r g b)

color #allow_actions( conversion_from[_integer] )

conversion_from = #actions [$hint: from_type] (
  as = #fn_params [$type: to_type] (from_type: this) : to_type
)

conversion_from[$int].as[color] = #fn (_integer: this) : color {
  [#ret.r #ret.g #ret.b] = (this !make_byte_array: [3 2 1])
}

Ещё можно придумать команду #allow_actions_only, исключающую применение повторябельной #allow_actions

  • Для GC я написал на плюсах optr (он учитывает цикличные линки), но пока его не тестировал сильно (версия-прототип)
  • И это не GC)