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

Сообщение Re[3]: стоит создать ЯП? от 11.10.2023 12:57

Изменено 11.10.2023 13:18 Sm0ke

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

N>Тогда давай начнёшь с определения целей.


N>1. Общая направленность — системный, прикладной на какой-то класс задач.


Назначение языка — для любых задач.

N>2. Процедурный, функциональный, что-то посредине.


Структура файла:
* Имя модуля
* Декларативная часть (определение ячеек модуля, тоесть свойств модуля)

Элемент декларативной части начинается с имени ячейки, или с разрешённой для декларативной части команды.
Ячейка может содержать что угодно. И тип, и функцию, и последовательность выражений (sequence), и просто значение

Таким образом не нужно писать как в си++ using type_alias = int;
А просто type_alias = $int
fn_alias = other_fn


S>>*- Императивный кусок кода помещается в фигурные скобки и имеет тип $seq (sequence).

N>Тогда надо сразу сказать, что будет в неимперативных кусках.

В декларативной части можно:
имя_ячейки = ( /* expression */ )
cell_name_other = /* команда опеделения */
cell_name_seq = { /* последовательность выражений, тут императивная часть */ }
имя_ячейки /* команда-модификатор */

Вот некоторые команды для создания новых определений: (список не полный)
  • #struct
  • #enum
  • #fn
  • #nest
  • #actions // интерфейс, набор методов

N>3. Управление памятью: ручное (как в C, C++), автоматическое полностью (как в Java, C#), на владении ссылками (Rust), смешанное.


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

Вообще неплохо-бы предусмотреть способ указания для желаемого поведения в самой программе на кси (для low level штук).
Например чтобы не происходило автоосвобождение большого колличества мелких объектов по отдельности, а пачкой
(ещё когда при закрытии приложении ОС сама освобождает выделенную память)

N>4. Уровень фиксации типизации — статическая, динамическая.


Все Три вида типизации: ~ по трём отдельным обработчикам

Динамическая типизация ~ ksi-script
Строгая типизация ~ ksi-pl
динамическая + строгая ~ ksi-lang

Каждый обработчик может быть как компилятор, так и интерпретатор. А может быть два в одном.
Это уже нюанс реализации обработчика.

N>5. Уровень жёсткости типизации. Например, насколько разрешён integral promotion и по каким правилам.


Есть шаблонный метод x !as[type] , который вызывается в программе-скрипте явно для преобразования типов.
В квадратных скобках указывается параметр шаблона — тип результата преобразования.
Методы в кси вызываются не через точку. Точка служит для обращения к элементам (свойствам).
А методы задаются отдельно в так называемом интерфейсе (команда #actions)

conversion_from = #actions [Source_type] (
  as = #fn_params [Target_type] (Source_type: src) : Target_type
  implicit_as = #fn_params [Target_type] (Source_type: src) : Target_type
  
  /* implicit_as = as */
  /* same params of function */
)

Рассмотрим на примере. Если мы хотим преобразование явного вида из $int в $float тогда в декларативной части:
$float #allow_actions( conversion_from[$int] )

conversion_from[$int].as[$float] = #fn ($int: number) : $float { #ret = /* какая-то системная функция */ }

do = {
 i = 5
 f = (i !as[$float])
}

Если действие implicit_as не задано, то неявное преобразование запрещено обработчиком.
Но мы можем при желании его задать одной строчкой:
conversion_from[$int].implicit_as[$float] = conversion_from[$int].as[$float]

Короче сделать алиас из уже написанной функции implicit_as = as
В этом случае будет работать неявное преобразование. Программист это может контролировать сам в том числе и для своих типов.

N>Ответы желательно развёрнуто. Это только для разгона, после этого можно будет выходить на второй круг.


S>>Мне кажется более наглядным, когда в программе данные определяются отдельно от действий над ними.

N>Для большинства это менее наглядно, но пусть. Что-то похожее есть в Rust с его impl-ами для типа. Смотрел это? Какие впечатления?

Надо будет посмотреть как там impl в Rust

S>>*- Имена системных типов начинаются к примеру с символа $ (но это можно указать в конфиге, как и другие начальные символы).


N>Неееет!!!! Это однозначный путь к бардаку. Хватит нам и споров snake_case/CamelCase/mixedCase. Ты представляешь себе, что будет, если ты будешь подключать файл из одного такого режима к проекту с другим режимом?


N>Подобные элементы синтаксиса обязаны быть жёсткими и безальтернативными!


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

S>>Таким образом добавление системных типов не будет конфликтовать с уже написанным пользовательским кодом.


N>Для такого давно придумали пространства имён. Но редкие конфликты с автоматической переделкой — не проблема.


В кси — модули. Хотя пространства имён возможно тоже понадобятся (или нет). Есть #nest для nested штук.
@sys.int просто $int писать не так длинно
@sys.write_line или &write_line

S>>* По сути sequence — это как лямбда без параметров.

N>Только с автозахватом из внешнего контекста? Уточни.

Да, с автозахватом (closure)

Мб добавить возможность задания seq без захвата локального внешнего контекста через конструкцию #{ } ?

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


N>А зачем его вообще писать, ты ведь уже назвал "ячейку"?


Ячейка if_first хранит функцию. Способы вызова функции:
  • sinus(p)
  • sinus! p
  • p |sinus
  • p |some_fn(second_param, third_param)
  • p |some_fn: second_param

Функции if_first if_each принимают один параметр, с типом $seq
Я бы по хорошему их сделал системными.

Ключевых слов в кси стараюсь вообще избегать.
К примеру можно определить системную функцию &do_while цикл с пост-условием
@global /* exposition only */
&do_while = #fn($seq: loop_body, condition)

@main

do = {
  { /* loop body */ } |&do_while: { /* condition */ }
}

Или как метод в seq_actions
@main

do = {
  { /* loop body */ } !do_while: { /* post-condition */ }
  { /* pre-condition */ } !while_do: { /* loop body */ }
}

@global /* system exposition */

seq_actions = #actions ( do_while, while_do )

Вместо if можно сделать метод !then
{ a == b } !then: { seq }

Но тут нюанс с !else пока не решён)

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

N>Вместо LF или вообще? Надо уточнить.

LF и запятая — опциональные разделители, хотя LF оно как WHITEPSACE
А запятая с проверкой в парсере.

S>>coords = #struct ( x y )


N>Я бы делал, что есть набор слов, которые по умолчанию опознаются как ключевые (например if, while, func), а если нужно их сделать идентификаторами, то поставить, например, $ впереди.


#struct — это системная команда.
Какой начальный символ для команд, а какой для системных типов лучше подобрать?
Я предлагаю для команд #
Для типов $
Для функций &
Это просто строчки в конфиге обоаботчика. С другой стороны нужен общепринятый формат)

Ключевые слова? keywords — нужны ли они вообще в кси? Если да, то какие?
Можно сделать bitwise методы для integer types (выглядят как именованные операторы bit_xor, bit_and, bit_shift +1 -1)

S>>Ячейка результата вызова функции #ret

N>По-моему, этот подход в основном признан менее удачным, чем явное return как оператор.

Добавить команду #return не проблема)

S>>Ведь структуры передаются по указателю, как и прочие значения системной категории _compound


N>Это плохо. Например, математики тебя побьют. struct complex { float real, imag; } — во многих ABI передаётся в двух регистрах. Это эффективно. А ты предлагаешь откатиться обратно.


Значит нужна возможность задать для структуры как она передаётся ( _by_value или _by_pointer )
Например через категории типов.
@math

complex = #struct ( $float: real imag )

complex #refers ( _by_value ) // привязали категорию для типа


S>>Все категории типов (значение $cat) произрастают из базовой _any, включённой во все типы по-умолчанию.

S>>Категория — это просто категория, она не хранит в себе данных. Каждый тип может принадлежать своему набору категорий.
S>>В программе можно проверить их принадлежность. Это может быть полезно при динамической типизации.

N>Интерфейсы или типажи (traits)? Есть причина вводить новое слово для того же?


Нет, это не интерфейсы. Про интерфейсы я ответил выше как #actions
Категории ещё могут быть абстрактными (с запретом к привязки для типов)
Для специальных значений, или сигналов об ошибке.
@sample

func1 = #fn (param) : $cat_options[_key_absent, _ok]
{ #ret = _ok }

func2 = #fn (param) : $cat_under[_null] // subcategories of _null
{ #ret = _ok }

@global

#cats (
  _null #includes ( _abstract )
  _null #subcat ( _undefined _key_absent _no_such_action _ok )
)

@maybe

void_type = $cat_exact[_ok]


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

N>Нужны. Только продумать надо.

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

S>>Ведь тип $cat пренадлежит категории _map_key

N>Вообще обычно говорят более математически — hashable или linearly orderable.

Это похоже на набор методов для работы с данными.
ordering = #enum ( strong weak partial )

cmp_result = /* ? */

comparable = #actions [ordering: O, $type: left_type right_type] (
  cmp = #fn_params (left_type: left, right_type: right) : cmp_result

  (<) = #fn (left_type: left, right_type: right)
  { #return( (left !cmp: right) == cmp_result.less ) }
)


Стандыртные наборы действий (интерфейсы) для системных типов необходимо определить.
Желательно совместными усилиями, а не в одиночку.

N>Итого

N>У тебя нет killer feature. Без неё это не выйдет за пределы одного условного диссера.

А как-же работа с типами как с обычными значениями?
Вместо плюсовых <type_traits> std::is_same_v<> и using type =

А как же встроенная система атрибутов. Позволяющая в том числе и рефлексию (интроспекцию).
Работать со списком/мапой элементов перечисления, как с обычным массивом.
Так-же со списком свойств структуры / неста.
Атрибут type->name текстовая строка.

Возможность добавлять пользовательские конструкции над seq как функции.
Re[3]: стоит создать ЯП?
Здравствуйте, netch80, Вы писали:

N>Тогда давай начнёшь с определения целей.


N>1. Общая направленность — системный, прикладной на какой-то класс задач.


Назначение языка — для любых задач.
И для gui, и для научных расчётов, и как встроенный скриптовый движок приложений/игр.

N>2. Процедурный, функциональный, что-то посредине.


Наверно это микс.

Структура файла:
* Имя модуля
* Декларативная часть (определение ячеек модуля, то есть свойств модуля)

Элемент декларативной части начинается с имени ячейки, или с разрешённой для декларативной части команды.
Ячейка может содержать что угодно. И тип, и функцию, и последовательность выражений (sequence), и просто значение

Таким образом не нужно писать как в си++ using type_alias = int;
А просто type_alias = $int
Аналогично для функций:
fn_alias = other_fn


S>>*- Императивный кусок кода помещается в фигурные скобки и имеет тип $seq (sequence).

N>Тогда надо сразу сказать, что будет в неимперативных кусках.

В декларативной части можно:
имя_ячейки = ( /* expression */ )
cell_name_other = /* команда опеделения */
cell_name_seq = { /* последовательность выражений, тут императивная часть */ }
имя_ячейки /* команда-модификатор */

Вот некоторые команды для создания новых определений: (список не полный)
  • #struct
  • #enum
  • #fn
  • #nest
  • #actions // интерфейс, набор методов

N>3. Управление памятью: ручное (как в C, C++), автоматическое полностью (как в Java, C#), на владении ссылками (Rust), смешанное.


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

Вообще неплохо-бы предусмотреть способ указания для желаемого поведения при конкретном случае в самой программе на кси (для low level штук).
Например чтобы не происходило автоосвобождение большого колличества мелких объектов по отдельности, а пачкой
(ещё когда при закрытии приложении ОС сама освобождает выделенную память)

N>4. Уровень фиксации типизации — статическая, динамическая.


Все Три вида типизации: ~ по трём отдельным обработчикам
Можно сказать — три диалекта языка.

Динамическая типизация ~ ksi-script
Строгая типизация ~ ksi-pl
динамическая + строгая ~ ksi-lang

Каждый обработчик может быть как компилятор, так и интерпретатор. А может быть два в одном.
Это уже нюанс реализации обработчика.

N>5. Уровень жёсткости типизации. Например, насколько разрешён integral promotion и по каким правилам.


СИ-шных ошибок в проектировании языка желательно избежать!

Есть шаблонный метод x !as[type] , который вызывается в программе-скрипте явно для преобразования типов.
В квадратных скобках указывается параметр шаблона — тип результата преобразования.
Методы в кси вызываются не через точку (а через воскл.знак перед методом).
Точка служит для обращения к элементам (свойствам).
А методы задаются отдельно в так называемом интерфейсе (команда #actions)

conversion_from = #actions [Source_type] (
  as = #fn_params [Target_type] (Source_type: src) : Target_type
  implicit_as = #fn_params [Target_type] (Source_type: src) : Target_type
  
  /* implicit_as = as */
  /* same params of function */
)

Рассмотрим на примере. Если мы хотим преобразование явного вида из $int в $float тогда в декларативной части:
$float #allow_actions( conversion_from[$int] )

conversion_from[$int].as[$float] = #fn ($int: number) : $float { #ret = /* какая-то системная функция */ }

do = {
 i = 5
 f = (i !as[$float])
}

Если действие implicit_as не задано, то неявное преобразование запрещено обработчиком.
Но мы можем при желании его задать одной строчкой:
conversion_from[$int].implicit_as[$float] = conversion_from[$int].as[$float]

Короче сделать алиас из уже написанной функции implicit_as = as
В этом случае будет работать неявное преобразование. Программист это может контролировать сам в том числе и для своих типов.

N>Ответы желательно развёрнуто. Это только для разгона, после этого можно будет выходить на второй круг.


S>>Мне кажется более наглядным, когда в программе данные определяются отдельно от действий над ними.

N>Для большинства это менее наглядно, но пусть. Что-то похожее есть в Rust с его impl-ами для типа. Смотрел это? Какие впечатления?

Надо будет посмотреть как там impl в Rust

S>>*- Имена системных типов начинаются к примеру с символа $ (но это можно указать в конфиге, как и другие начальные символы).


N>Неееет!!!! Это однозначный путь к бардаку. Хватит нам и споров snake_case/CamelCase/mixedCase. Ты представляешь себе, что будет, если ты будешь подключать файл из одного такого режима к проекту с другим режимом?


N>Подобные элементы синтаксиса обязаны быть жёсткими и безальтернативными!


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

S>>Таким образом добавление системных типов не будет конфликтовать с уже написанным пользовательским кодом.


N>Для такого давно придумали пространства имён. Но редкие конфликты с автоматической переделкой — не проблема.


В кси — модули. Хотя пространства имён возможно тоже понадобятся (или нет). Есть #nest для nested штук.
@sys.int просто $int писать не так длинно
@sys.write_line или &write_line

S>>* По сути sequence — это как лямбда без параметров.

N>Только с автозахватом из внешнего контекста? Уточни.

Да, с автозахватом (closure)

Мб добавить возможность задания seq без захвата локального внешнего контекста через конструкцию #{ } ?

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


N>А зачем его вообще писать, ты ведь уже назвал "ячейку"?


Ячейка if_first хранит функцию. Способы вызова функции:
  • sinus(p)
  • sinus! p
  • p |sinus
  • p |some_fn(second_param, third_param)
  • p |some_fn: second_param

Функции if_first if_each принимают один параметр, с типом $seq
Я бы по хорошему их сделал системными.

Ключевых слов в кси стараюсь вообще избегать.
К примеру можно определить системную функцию &do_while цикл с пост-условием
@global /* exposition only */
&do_while = #fn($seq: loop_body, condition)

@main

do = {
  { /* loop body */ } |&do_while: { /* condition */ }
}

Или как метод в seq_actions
@main

do = {
  { /* loop body */ } !do_while: { /* post-condition */ }
  { /* pre-condition */ } !while_do: { /* loop body */ }
}

@global /* system exposition */

seq_actions = #actions ( do_while, while_do )

Вместо if можно сделать метод !then
{ a == b } !then: { seq }

Но тут нюанс с !else пока не решён)

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

N>Вместо LF или вообще? Надо уточнить.

LF и запятая — опциональные разделители, хотя LF оно как WHITEPSACE
А запятая с проверкой в парсере.

S>>coords = #struct ( x y )


N>Я бы делал, что есть набор слов, которые по умолчанию опознаются как ключевые (например if, while, func), а если нужно их сделать идентификаторами, то поставить, например, $ впереди.


#struct — это системная команда.
Какой начальный символ для команд, а какой для системных типов лучше подобрать?
Я предлагаю для команд #
Для типов $
Для функций &
Это просто строчки в конфиге обоаботчика. С другой стороны нужен общепринятый формат)

Ключевые слова? keywords — нужны ли они вообще в кси? Если да, то какие?
Можно сделать bitwise методы для integer types (выглядят как именованные операторы bit_xor, bit_and, bit_shift +1 -1)

S>>Ячейка результата вызова функции #ret

N>По-моему, этот подход в основном признан менее удачным, чем явное return как оператор.

Добавить команду #return не проблема)

S>>Ведь структуры передаются по указателю, как и прочие значения системной категории _compound


N>Это плохо. Например, математики тебя побьют. struct complex { float real, imag; } — во многих ABI передаётся в двух регистрах. Это эффективно. А ты предлагаешь откатиться обратно.


Значит нужна возможность задать для структуры как она передаётся ( _by_value или _by_pointer )
Например через категории типов.
@math

complex = #struct ( $float: real imag )

complex #refers ( _by_value ) // привязали категорию для типа


S>>Все категории типов (значение $cat) произрастают из базовой _any, включённой во все типы по-умолчанию.

S>>Категория — это просто категория, она не хранит в себе данных. Каждый тип может принадлежать своему набору категорий.
S>>В программе можно проверить их принадлежность. Это может быть полезно при динамической типизации.

N>Интерфейсы или типажи (traits)? Есть причина вводить новое слово для того же?


Нет, это не интерфейсы. Про интерфейсы я ответил выше как #actions
Категории ещё могут быть абстрактными (с запретом к привязки для типов)
Для специальных значений, или сигналов об ошибке.
@sample

func1 = #fn (param) : $cat_options[_key_absent, _ok]
{ #ret = _ok }

func2 = #fn (param) : $cat_under[_null] // subcategories of _null
{ #ret = _ok }

@global

#cats (
  _null #includes ( _abstract )
  _null #subcat ( _undefined _key_absent _no_such_action _ok )
)

@maybe

void_type = $cat_exact[_ok]


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

N>Нужны. Только продумать надо.

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

S>>Ведь тип $cat пренадлежит категории _map_key

N>Вообще обычно говорят более математически — hashable или linearly orderable.

Это похоже на набор методов для работы с данными.
ordering = #enum ( strong weak partial )

cmp_result = /* ? */

comparable = #actions [ordering: O, $type: left_type right_type] (
  cmp = #fn_params (left_type: left, right_type: right) : cmp_result

  (<) = #fn (left_type: left, right_type: right)
  { #return( (left !cmp: right) == cmp_result.less ) }
)


Стандыртные наборы действий (интерфейсы) для системных типов необходимо определить.
Желательно совместными усилиями, а не в одиночку.

N>Итого

N>У тебя нет killer feature. Без неё это не выйдет за пределы одного условного диссера.

А как-же работа с типами как с обычными значениями?
Вместо плюсовых <type_traits> std::is_same_v<> и using type =

А как же встроенная система атрибутов. Позволяющая в том числе и рефлексию (интроспекцию).
Работать со списком/мапой элементов перечисления, как с обычным массивом.
Так-же со списком свойств структуры / неста.
Атрибут type->name текстовая строка.

Возможность добавлять пользовательские конструкции над seq как функции.

--

Постораюсь ответить на вопросы, которые ещё остались неотвечены.

То что я написал — моё приблизительное представление.
На основе этого можно же сделать better language, как бы он не назывался