Войти
ФлеймФорумПрограммирование

Революционные идеи в сфере языков программирования

Страницы: 1 2 3 4 5 6 Следующая »
#0
7:22, 18 сен. 2018

Признайтесь, у каждого из нас были идеи нового языка.
У меня тоже есть ряд идей.
И у меня возникла трудность.

Собственно, для контекста, краткое описание принципов работы этого вымышленного языка.
Главная форма подпрограммы - это "оператор". Операторы настолько важны в этом языке, что для них отводится отдельное пространство имён.
Большинство происходящих в программе вещей - это вызовы операторов.
Их можно вызывать явно:

common_alignment = least_common_multiple[alignment_1, alignment_2]

Стандартные плюсы-минусы - это альтернативная форма сериализации вызова оператора. То есть, уже на уровне парсинга, выражение
"Hello, " .. name

даёт AST, который абсолютно идентичен
concat["Hello, ", name]

Кроме того, многие автоматические конструкции так же выражаются в виде вызовов операторов. Сюда включается, например, инициализация и финализация значений:
do
    -- вызывает init[receive prefix, "Hello, "]
    state prefix = "Hello, "
    -- вызывает init[receive message]
    state message: string
    message = prefix + name
    print[message]
    -- вызывает delete[message]
    -- вызывает delete[prefix]
end

Операторы могут иметь от нуля до неопределённо большого числа входящих значений и от нуля до неопределённо большого числа результатов, среди которых один результат может быть выделен как главный.
Операторы перегружаются как по входящим, так и по выходящим аргументам. Это, например, позволяет использовать механизм операторов для инициализации.
Методы не выделяются в отдельную категорию, а так же рассматриваются как частный случай операторов. Переопределение метода в наследнике в этом языке является частным случаем перегрузки оператора.

Важный аспект языка - намеренное отсутствие оператора взятия адреса. Значения, передаваемые по ссылкам, гарантированно находятся в куче и почти наверняка видимы и изменяемы сторонним кодом, возможно даже в параллельном потоке. Для стековых же переменных, напротив, единственный способ изменить значение - это явно обратиться к ней по имени, что позволяет дать определённые гарантии.
Как следствие - множественные возвраты должны быть явно прописаны как минимум в месте объявления оператора.
Я как хозяин вымышленного языка неявному предпочитаю явное, поэтому выходые параметры так же должны помечаться и в месте вызова - например, префиксами modify и receive.

buffer = read[modify stdin, length, receive advance]

Привязка аргументов по именам мне кажется очень удобной фичей. Разумеется, вместе с ними так же имеет смысл разрешить объявлять непозиционные параметры - которые привязываются к аргументам только по имени. Разумеется, именованные аргументы участвуют в перегрузке точно так же, как и позиционные.

-- эквивалентное объявление:
operator read[
    inout source: istream,
    in requested_length: uint32,
    nonpositional
    default out data: bytestring,
    out actual_length: uint32]
-- использование:
buffer = read[
    source -> modify stdin,
    requested_length -> length,
    actual_length -> receive advance]

Достаточно часто для значения, возвращаемого оператором, создаётся совершенно новая переменная. Поскольку отдельное объявление, помимо неудобности, ещё и приводит к дополнительным действиям на инициализацию и переписывание, то так же имеет смысл позволить объявлять переменные прямо в момент получения значения.

state buffer: string = read[ -- автоматически ищет и вызывает init[out : string, : bytestring]
    source -> modify stdin,
    requested_length -> length,
    actual_length -> receive state advance] -- автоматически ищет и вызывает init[out, : uint64]

Наконец, иногда - для пропуска позиционного аргумента или для управления выбором перегрузок - имеет смысл пропустить запись результата в переменную, при этом оставляя возможность указать тип.

state buffer: string = read[
    source -> modify stdin,
    requested_length -> length,
    actual_length -> ignore : uint32]

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

procedure main[]
    state io = from environment request io[]
    io = io after request write["What is your name?"] -- 1
    io = io after request readline[
        line -> state name] -- 2
    io = io
        after request write["Hello, "] immediately -- 3
        after request write[name] immediately -- 4
        after request write["\n"] -- 5
    current = synchronize current with io
end

Семантически, io after request write[...] копирует имя "write" и значения аргументов в отдельно созданный объект запроса, отсылает его по io.address с маркировкой io.barrier и возвращает управление немедленно вместе с новым хендлом, в котором записан тот же адрес, но в поле барьера уже стоит значение "после запроса --1".
Следующий io after request readline[...] создаёт и отправляет новый запрос --2 немедленно. Однако, поскольку к этому запросу приложен барьер "после --1", это накладывает ограничения на порядок выполнения запросов - запрос --2 не может начать своё выполнение прежде, чем закончит работу запрос --1.
Суффикс immediately генерирует барьер с дополнительным ограничением - помеченный им запрос --4 должен начать выполняться "сразу же" после окончания запроса --3. Под "сразу же" имеется в виду, что между этими запросами актёр по io.address не должен изменить своё состояние.
Такая семантика подобна функциональной парадигме - в каком-то смысле, результат after request представляет собой новое состояние объекта после изменений, вызванных запросом. Барьер же служит ограничением на множество версий, над которыми работает запрос - простой after request генерирует ограничение вида "любая версия не ранее такой-то", тогда как after request ... immediately генерирует "строго вот эта версия и никакая другая".
Поскольку барьер, выданный в ответ на запрос --2 не помечен этим маркером, то между --2 и --3 io может претерпеть сколько угодно изменений от параллельных потоков. Более строгий барьер между --3 и --4 гарантирует, что между ними над io не будут вызваны изменяющие запросы; однако же, считывающие запросы типа
if from io request is_console[] then
elseif from io request is_file[] then
elseif from io request is_pipe[] then
else
end

могут прийтись как раз на этот промежуток, если только их барьер не является таким же after request ... immediately.
Наконец, последняя строчка current = synchronize current with io поднимает зависимость до самой процедуры main[]. В специальной переменной current хранится актёр, описывающий состояние выполнения текущей процедуры - локальные переменные и описание, какая часть кода уже выполнена, а какая - всё ещё ждёт ответа. Значение current.barrier, с которым процедура заканчивает работу, так же наделяется особым смыслом - к нему привязывается барьер на вызывающей стороне. Таким образом, запрос вида myprogram after request main[] не будет считаться завершённым, пока не закончит свою работу запрос --5. Без этой строки, процедура считалась бы полностью завершённой сразу после отправки всех описанных в коде запросов.

Ну и, собственно, в чём проблема - по-моему, синтаксис, которым я записал примеры выше, корявый и можно сделать лучше, но у меня нет идей для стоящих альтернатив.


#1
8:44, 18 сен. 2018

Lua

#2
16:08, 18 сен. 2018

Синтаксис в котором можно что-то убрать и ничего не изменится - корявый, и избыточный. Убирай всё что можно убрать. Это важно.

При передаче аргументов - Слова modify и receive это глаголы, нужны прилагательные или указание направления для выходящего результата, например mutable xxx, и out yyy. Я у тебя насчитал штук 6 модификаторов для аргументов, это как-то сложновато что-ли? Упрощай.

Смешивать вместе параметры входящие и выходящие результаты это вообще ад. Раздели их визуально. Возьми пример из приятного тебе языка. Например Rust - функция принимает мутабельную ссылку на int32 (это у тебя аргументы receive получается) и uint32, а возвращает тройку (кортеж из 3) - логический, int32 и std::string:

pub fn govno(arg1: &mut i32, arg2: u32) -> (bool, i32, String) {}

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

Имитация хаскельной монады ио - это полная задница. Такой язык никто не захочет учить. Сами хаскелисты толком не могут объяснить зачем это было сделано. Кот должен исполняться прямо сверху вниз, и выглядеть приятным для чтения.

Лучше всего позволить каждый алгоритм писать прямо без async/await, и имитировать параллельные процессы или треды или файберы хотя бы на уровне синтаксиса - то есть в месте где вызывается другая асинхр функции твой тред будет засыпать и ждать автоматически, не мешая другим. Читать такой кот будет гораздо приятнее. Задача нетривиальная я понимаю, но так будет гораздо удобнее.

#3
17:04, 18 сен. 2018

kvakvs
> Синтаксис в котором можно что-то убрать и ничего не изменится - корявый, и избыточный.
Категорически несогласен. Сравни

+ Показать

и
+ Показать

С моей точки зрения, конструкция, которая передаёт намерения программиста явно, предпочтительнее конструкции, в которой намерения только подразумеваются.

kvakvs
> Возьми пример из приятного тебе языка. Например Rust
неприятен мне как раз потому, что сильно наседает на сокращения.
Слова "modify" и "receive" служат как минимум двум целям - они явно передают намерения вызывающего (вот эта переменная поменяет своё значение, а вот у этой - перезапишется без чтения старого) и так же позволяют компилятору статически проверить место вызова на соответствие прототипу. В этом смысле глаголы "modify" и "receive" играют ту же роль, что и оператор взятия адреса в си, с рядом отличий: они явно указывают, читается ли из переменной её старое значение; кроме того, семантически, вызываемый оператор всё равно получает свою копию и изменяет её локально, а изменение в переменной вызывающей стороны происходит только на выходе из оператора, в виде присваивания возвращённого оператором значения прежней переменной.

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

kvakvs
> Про асинхронность почитай как работает концепция async/await, это упростит тебе
> тысячи костылей :)
Я в курсе, и это не та модель, которая используется в этом языке.
В этом вымышленном языке используется модель, больше похожая на Erlang - каждый актёр живёт в своём собственном процессе, а обмен информацией происходит исключительно через сообщения. Вымышленный язык добавляет к этому встроенный механизм ответов на сообщения (в виде out/receive-аргументов и барьеров) и соответствующие механизмы синхронизации.
Монадная форма тогда получается почти естественным способом записи временных зависимостей - если операция Б должна ждать завершения А, то это делается созданием зависимости через данные, когда на один из входов Б подаётся один из результатов А.
По задумке, поскольку во многих случаях зависимости проходят именно по данным, то для значительной доли алгоритмов правильный граф зависимостей получится автоматически из их естественной записи.

kvakvs
> Я у тебя насчитал штук 6 модификаторов для аргументов, это как-то сложновато что-ли?
Разве? Я вижу только три вида передачи в целом - вход, выход и двусторонний.

#4
17:16, 18 сен. 2018

Ну и вообще, какой смысл изобретать, например, альтернативный раст, если уже есть раст обычный? Суть этого языка состоит в том числе и в его нестандартной модели выполнения - когда язык не имитирует выполнение сверху вниз ценой назальных демонов и [[carries_dependency]] (даже не знаешь, что из этого хуже), а выносит асинхронность в базовые принципы и предоставляет программисту, хотелось бы надеяться, более удобные и вместе с тем более точные инструменты для её покорения, чем std::memory_order, std::mutex и, может быть, даже async/await; хотя в каком-то смысле отправка "after request" и использование результата в следующей операции - это и есть async и await этого языка, которые просто принимаются за нормальный режим работы и поэтому не получают такого особого обращения.

#5
17:21, 18 сен. 2018

Delfigamer
> Категорически несогласен. Сравни
Оба примера больно читать.
Но за код в первом стиле я бы долго пинал труп того, кто подал этот код на ревью, вплоть до полного переписывания. А код во втором стиле просто незнакомый и непонятно что он делает, но вроде бы читается хорошо, так что ладно.

У меня просто возникло ощущение что в твоём синтаксисе много избыточных деталей. Просто впечатление такое.

Delfigamer
> В этом вымышленном языке используется модель, больше похожая на Erlang - каждый
> актёр живёт в своём собственном процессе, а обмен информацией происходит
> исключительно через сообщения. Вымышленный язык добавляет к этому встроенный
> механизм ответов на сообщения (в виде out/receive-аргументов и барьеров) и
> соответствующие механизмы синхронизации.
Ну вот мы и вышли на правильную реализацию.

Прошу заметить что в Э нету монады ио, у тебя есть. Потому что Э язык не ленивый и тащить ио, чтобы обмануть ленивый компилятор в Э не требуется. То есть не нужна, следует избавиться.
Во-вторых в Э нет асинхронности, все вызовы синхронны и блокируют соответствующий процесс. У тебя асинхронность и поддержка в синтаксисе есть. Зачем? Не знаю, тоже следует избавиться.

#6
17:25, 18 сен. 2018

Delfigamer
> Ну и вообще, какой смысл изобретать, например, альтернативный раст, если уже
> есть раст обычный? Суть этого языка состоит в том числе и в его нестандартной
> модели выполнения - когда язык не имитирует выполнение сверху вниз ценой
> назальных демонов и [[carries_dependency]] (даже не знаешь, что из этого хуже),
> а выносит асинхронность в базовые принципы и предоставляет программисту,
> хотелось бы надеяться, более удобные и вместе с тем более точные инструменты
> для её покорения, чем std::memory_order, std::mutex и, может быть, даже
> async/await; хотя в каком-то смысле отправка "after request" и использование
> результата в следующей операции - это и есть async и await этого языка, которые
> просто принимаются за нормальный режим работы и поэтому не получают такого
> особого обращения.

Выхвачу из текста самое важное.
Нестандартная модель выполнения.
Как её читать?
Когда у тебя десятки асинхронных событий в 1 функции код превращается в асинхронную колбекоблевотину так любимую разработчиками яваскрипта и нодыжс. Почему бы не избежать этого с помощью изолированных процессов, которые выполняются сверху вниз, и удалением из синтаксиса этих асинк ключевых слов?

В том же Э нет мьютексов, мемори ордеров и прочей фигни потому что она надёжно закопана в виртуальной машине. Вот он идеал к которому нужно стремиться :)

#7
17:38, 18 сен. 2018

kvakvs
> Выхвачу из текста самое важное.
> Нестандартная модель выполнения.
> Как её читать?
Именно поэтому вводятся ограничения, типа: значение переменной можно изменить только там, где к ней можно обратиться по имени; а для установки порядка между запросами требуется явная передача данных между ними.

kvakvs
> В том же Э нет мьютексов, мемори ордеров и прочей фигни потому что она надёжно
> закопана в виртуальной машине. Вот он идеал к которому нужно стремиться :)
Ну и опять же - с задачей "быть эрлангом" хорошо справляется и сам эрланг, так что просто копировать его тоже смысла особого нет. :3

#8
18:22, 18 сен. 2018

Delfigamer
> > Выхвачу из текста самое важное.
> > Нестандартная модель выполнения.
> > Как её читать?
> Именно поэтому вводятся ограничения, типа: значение переменной можно изменить
> только там, где к ней можно обратиться по имени; а для установки порядка между
> запросами требуется явная передача данных между ними.
Это оставляет огромную свободу для создания нечитаемого кода, который невозможно проследить. Вообще просто идеально.

Delfigamer
> Ну и опять же - с задачей "быть эрлангом" хорошо справляется и сам эрланг, так
> что просто копировать его тоже смысла особого нет. :3
Ну количество языков на Э виртуальной машине уже приближается к десятку.

#9
18:56, 18 сен. 2018

Delfigamer
// я мимо проходил
Убери операторы.
Внедри шорт-кат-систему,
чтобы у некоторых важных процедур были ярлыки.

Тоесть, будет нечто _игровое програмирование,
когда частые нюк-процы кодер будет _шлёпать от ярлыка,
вместо того чтобы _начинать печатать слово,
и долбить ввод, когда в комбо-боксе авто-подставы будет нужное
полное название процедуры.

Ну и по желанию _ревизора_читателя, редактор может печатать
ярлыки-картинки, вместо оригинального названия процедур.

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

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

#10
1:28, 19 сен. 2018

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

"Тип" в этом языке, в первую очередь, - это предикат, который из всего множества в принипе возможных в языке значений отбирает определённое подмножество.
В чистом виде, тип - это аналог концептов из C++20, который в свою очередь является логическим развитием понятия интерфейса.

concept InputStream
    requires operator read[inout source: InputStream, requested_length: UInt32, out actual_length: UInt32]: ByteString
    requires operator iseof[source: InputStream]: Boolean
end

Концепт предоставляет статическую проверку сразу в двух местах.
Во-первых, при объявлении типа-реализации, если явно заявить поддержку концепта, но не завезти её - это будет семантической ошибкой.
import InputStream

class Deflate implements InputStream
    state source: InputStream
    state buffer: ByteString
end

operator read[inout source: Deflate, requested_length: UInt32, out actual_length: UInt32]: ByteString
    ---
end

-- семантическая ошибка при загрузке модуля
-- заявлена, но не реализована поддержка operator iseof[: Deflate]: Boolean


Во-вторых, набор операций, доступных в теле оператора, в норме ограничивается только затребованным типом.
operator dostuff[inout stream: InputStream, str: ByteString]
    if not[isempty[str]] then
        write[modify stream, str]
        -- семантическая ошибка при загрузке operator dostuff
        -- требуется поддержка не заявленного в прототипе operator write[inout : InputStream, : ByteString]
    end
end

Критерии концепта не ограничиваются операторами, в нём так же могут быть указаны условия на поля объекта, поддерживаемые запросы и даже на значения полей - что позволяет определять операторы для конкретных значений.
operator factorial[x: Integer]: Integer requires x == 0
    return 0
end

operator factorial[x: Integer]: Integer requires x > 0
    return factorial[x-1] * x
end


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

Сами типы также обладают ещё одним важным свойством - они являются такими же значениями, как числа, инстансы классов и хэндлы актёров. Это позволяет рассматривать шаблоны как функции, возвращающие тип.

operator IndexedContainer[Element: Type, Index: Type = Integer]: Type
    return concept Self
        requires operator length[: Self]: Index
        requires operator get[: Self, : Index]: Element
        requires operator set[inout : Self, : Index, : Element]
    end
end

operator total[row: IndexedContainer[Number]]: Number -- Number подходит и под Integer, и под Real
    state current: Number = 0
    -- length[row] и get[row, i] затребованы в объявлении и потому не вызывают ошибок
    for i in range[length[row]] do
        current = current + get[row, i]
        -- в рантайме, нулевая итерация вызывает operator add[0, : ActualTypeOfRowElement]
        -- результат присваивается current без конверсий, как бы по указателю
        -- дальнейшие итерации вызывают add[: ActualTypeOfRowElement, : ActualTypeOfRowElement]
    end
    return current
end


Ну и наконец, комбинация типов-значений и перегрузки по значению позволяет реализовать классы как концепты, для которых язык автоматически реализует
operator newinstance[: Type, : LayoutConditions]

Этот оператор, собственно, создаёт инстанс неопределённого вида, который тем не менее поддерживает затребованные в объявлении state-ы и поэтому без проблем подставляется в методы этого класса.

Одна из особенностей такой структуры - язык оставляет за рантаймом свободу выбрать такое представление объекта, которое больше подходит под его использование в каждой конкретной ситуации. Без видимого эффекта на семантике, рантайм может создавать инстансы с разными синхронизационными примитивами - очередь сообщений, монитор, номер версии для CAS, или же наоборот, создать инстанс без какой-либо синхронизации, если оптимизатор линеаризует все запросы в один поток.

#11
10:42, 19 сен. 2018

Delfigamer
> Концепт
Интерфейс
Delfigamer
> Критерии концепта не ограничиваются операторами
If/else

Я не понимаю к чему все эти переизобретения велосипедов, выливающиеся в incomprehensible mess.

#12
12:18, 19 сен. 2018

Delfigamer
Поздравляю, ты изобрёл traits из руста (и что-то подобное было в руби).
Они ведут себя приблизительно как интерфейсы из ООП, но по сути не являются интерфейсами, а просто подключаемыми к типу свойствами, которые не занимают дополнительной памяти во время выполнения, а хранятся прямо в типе данных.

А вот с созданием хрен знает чего по выбору рантайма ты погорячился имхо. Зачем мне в программе созданное хрен знает что с негарантированным типом? Я уверен это работать будет из рук вон плохо.

#13
12:53, 19 сен. 2018

Delfigamer
Добро пожаловать в клуб.

#14
17:02, 19 сен. 2018

romgerman
> > Концепт
> Интерфейс
Нет, интерфейсы - это строгое подмножество концептов.

> > Критерии концепта не ограничиваются операторами
> If/else
Уже обсуждали

> Я не понимаю
Я очень за тебя рад.

kvakvs
> А вот с созданием хрен знает чего по выбору рантайма ты погорячился имхо. Зачем
> мне в программе созданное хрен знает что с негарантированным типом? Я уверен
> это работать будет из рук вон плохо.
Эти изменения - на том же уровне, что __attribute__((packed)) и раскурочивание структур оптимизатором.
По задумке, этот язык интерпретируемый с JIT-компиляцией. Собственно, это утверждение позволяет райнтайму не только перекраивать схему объекта в памяти, но и свободно смешивать разные варианты одного объекта, поскольку фактический бинарный формат не угадывается из порядка полей в исходнике, а деталях расписывается в RTTI для каждого конкретного варианта.

Страницы: 1 2 3 4 5 6 Следующая »
ФлеймФорумПрограммирование