Search
Write a publication
Pull to refresh

Comments 42

А в чём смысл этой статьи? Вы хотя бы небольшой обзор языка сделали бы, с примерами и т.д.

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

Это прекрасно, только язык заявлен как потоково-ориентированный (что бы это не значило) а в папке examples на гитхабе все примеры какие то не потоковые - hello world, 99 бутылок, fizbuz и числа Фибоначчи. А где же обработка потоков на которой этот язык вроде как может показать себя во всей красе? Тема то большая и сложная и до сих пор всё ещё мало изученная, вроде бы есть где развернуться, но почему то нет.

На самом деле 99 бутылок вполне потоковый - на старте создается поток из 99 сообщений и мы сразу его обрабатываем, одно за другим. Но я согласен, что нужны другие примеры. Учту

Есть мнение что компиляция от транспиляции отличается уровнем абстракции. Нева и Го, который она генерирует, на очень разных уровнях абстракции, в отличии от, например, TS->JS. Есть много языков которые компилируются в C. Или, вот, Gleam компилируется в Erlang (даже не в Beam IR)

Но у нас нет и не будет своего бэкенда для машинного языка, это правда

Это не статья, а новость (Хабр при публикации просит указать тип публикации) о том, что вышла новая версия. Только и всего. Но вы правы, пожалуй, в том, что стоило начать не с новости, а с более подробной статьи

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

Было бы неплохо дать не только ссылку на репозиторий, а написать немного про язык для тех, кто о нем ничего не знает. В частности, описать задачи, в которых программирование на основе узлов и пересылки сообщений выигрывает у чистого Go (или с чем вы сравниваете). Если выигрывает по наглядности\читаемости - нужны примеры листингов со сравнениями, если по производительности - бенчмарки, и т.д.

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


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

> В частности, описать задачи, в которых программирование на основе узлов и пересылки сообщений выигрывает у чистого Go (или с чем вы сравниваете). Если выигрывает по наглядности\читаемости - нужны примеры листингов со сравнениями, если по производительности - бенчмарки, и т.д.

Согласен. Сравниваем в целом со всеми традиционными (control-flow) языками, но особенно с Go. И да, такие примеры действительно нужны. Что касается производительности - язык находится в альфа-версии, даже не все фичи еще готовы, так что о бенчмарках пока думать рано. Мы ищем early-adopters и контрибьюторов, в первую очередь. Спасибо за фидбек!

Хотелось бы увидеть описание, для чего и какой области он более будет эффективным, чем существующие решения.Глянул примеры, и пока кажется, что в более сложных сценариях эти стрелочки будут выглядеть не очень понятно.

:start -> 'Hello, World!' -> println -> :stop



Концепция интересная: каждая следующая команда имеет на входе результат предыдущей , поэтому вместо обычной записи c(b(a())) можем иметь более наглядную а -> b -> c. Но кмк более наглядная была бы запись:

a

b

c

Надо больше углубиться в эту тему.

P.S. Это что-то сродни переходу от лисповой записи с кучей лишних скобок к питоновской записи без лишних скобок (){} и соответственно тоже повышает читаемость.

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

P.P.S. Мне это видится аналогией функциональному программированию, но более читаемо и более линейно.

Соответственно, можно компилировать транспилировать не только в Go, а даже более естественно в любой функциональный язык, например Хаскель, собственно, как и в любой современный язык, хотя бы частично близкий к ФП, да тот же Rust, да хоть в JS, а далее избавиться и от этой прослойки и написать собственный компилятор.

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

Как мне представляется, и я, может, ошибаюсь, то, что назвали языком программирования, это более выглядит как DSL (domain specific language) на базе Go для потоковой обработки данных, хотя пишут, что он язык программирования общего назначения. Стрелочки — это направление потоков данных, и это как pipe (вполне как в Linux, можно использовать вместо стрелок |), но я пока не понял, как они будут сложные потоки данных разруливать, осуществляться проверка типов в цепочке вызовов и обратное распространение ошибки по стеку вызовов в корень, так как примеров более сложных не увидел в примерах. И главное, я считаю, логирование и дебаг неясно как осуществляется, если вставлять fmt.Println везде, выглядит не очень продуктивно. И уже прикинул, например, что для 2000 элементов вызовов будет очень много стрелочек, и выглядеть будет трудночитаемо (:start -> тут 2000 элементов -> :stop). Черты, присущие функциональным языкам программирования, я не заметил, хотя смотрел поверхностно. Первый релиз был в 2021 году, и удивительно, что я впервые только сейчас узнал о нем.С автором языка я согласен, что текущее выражение алгоритмов в текстовом файле я считаю устаревшим и необходимы более продуктивные технологии построения программных продуктов. 

Спасибо за комментарий. Вы не слышали о языке потому что он три года был в приватном репозитории, затем еще долго над ним велась работы, но без особой огласки и только недавно я начал заниматься его "маркетингом"

Из функционального тут неизменяемость данных + отсутствие ООПшных концепций таких как объекты-классы-методы-наследование + композиция и компоненты высшего порядка (как функции высшего порядка). Ближе всего это, наверно, к reactive functional programming, но с некоторыми особенностями

По поводу "-> тут 2000 элементов ->" - говнокодить можно на любом языке :) Тут как и везде есть абстракция (компоненты, пакеты). Большая программа будет структурирована соответствующим образом

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

А чем плохи функции высшего порядка? В вашу концепцию они вполне себе маппятся. Почему нельзя передать на вход функцию?

Как тогда делать lazy-вычисления?

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

Почему приняли решение их исключить? С моей колокольни это такой no go.

Я не то, чтобы решил их исключить, это просто вычислительная модель такова, что им нет места, потому что их место занимают другие вещи, которые работают иначе, решая ту же проблему

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

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

Звучит так, что ваши компоненты не замена функциям высшего порядка.

Функцию можно передать как аргумент в другую функцию. И в этом сила.

Ваше решение "постоянно запущен и ждёт сообщения" это, все таки, другое.

"постоянно запущен и ждёт сообщения" - это не про высший порядок, это про разницу в семантике между функциями и компонентами (control-flow vs dataflow)

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

но как сообщения компоненты передавать нельзя, так что определенные ограничения есть

Мы выбрали Go, потому что его конкурентная модель позволяет относительно легко реализовать многопоточную среду исполнения для языка. Все соединения между узлами конкурентны. Есть и другие причины, например, компиляция в машинный код или мощная стандартная библиотека

Запись со стрелочками это побочный эффект от потоко-ориентированной (dataflow/flow-based) парадигмы. Там разница именно в семантике в первую очередь

Концепция интересная: каждая следующая команда имеет на входе результат предыдущей , поэтому вместо обычной записи c(b(a())) можем иметь более наглядную а -> b -> c

Так это обычный pipe-оператор, который является одной из основных синтаксических конструкций в таких языках как Elixir и F#

На том же Elixir есть Flow - библиотека для потоковой обработки. Вот например, как посчитать частоту употребления слов в текстовом документе:

path_to_file
# потоковое чтение из файла
|> File.stream!()
# использовать IO-поток в качестве продьюсера для Flow
|> Flow.from_enumerable()
# разбить каждую строку по пробельным символам
|> Flow.flat_map(fn(line) -> String.split(line, ~r/\s+/) end)
# распараллелить дальнейшую обработку на несколько потоков
|> Flow.partition()
# отфильтровать то, что не является словами
|> Flow.filter(fn(word) -> Regex.match?(~r/^[\w-]+$/iu, word) end)
# выполнить свёртку с подсчётом слов
|> Flow.reduce(fn -> %{} end, fn(word, acc) ->
  Map.update(acc, word, 1, fn(count) -> count + 1 end)
end)
# преобразовать в список - реальные вычисления инициируются тут
|> Enum.to_list()
# отсортировать результат по убыванию частотности слов
|> Enum.sort(fn({_w1, count1}, {_w2, count2}) -> count1 >= count2 end)

А как эту же задачу решить на Neva?

P.S. Другое дело, что несмотря на удобство pipe-оператора писать 100% кода только в этом стиле - это неудобно. Поэтому глупо весь язык под это затачивать.

А как эту же задачу решить на Neva?

Спасибо за задачку, завел ишью и вернусь с решением

> несмотря на удобство pipe-оператора писать 100% кода только в этом стиле - это неудобно

Неудобно в control-flow языках, потому что они не созданы для этого - их вычислительная модель, основанная на вызовах функций и кол-стэке подразумевает четкий порядок исполнения, а dataflow работает иначе - вы описываете программу как граф для обмена сообщениями, каждое соединение работает параллельно (конкурентно, если точнее) остальным. Это совершенно иное, чем классическое ФП. И как вы теперь, возможно, понимаете - язык не "заточен под pipe operator", а pipe-operator просто отражает семантику dataflow

P.S - хочу заметить, что не совсем справедливо будет сравнивать читаемость динамического со статически-типизированным. Но в любом случае решение на Nevalang должно быть читаемым. Также хотел бы подметить, что Неваланг будет гибридным языком - с поддержкой визуального редактора, и наш текстовый синтаксис иногда может иметь компромиссные решения, для достижения конечной цели быть гибридом.

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

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

И я согласен, что это удобный подход для проектирования системы на верхнем уровне. Под неудобством я имел в виду, что внутренняя реализация акторов как раз является более простым кодом и там уже на каждый чих типа вывести "Hello world" в консоль продолжать думать потоками данных - это уже оверхед.

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

Супер! Давно такого ждал. Из похожего знаю только talend open studio (но он только графический), язык Occam и PlantUML (но это очень узкоспециализированный flow).Потоки "движения исполнения" да еще нормально до этого почти никто так и не делал. А ведь естественная штука.

На elixir это похоже, но все-таки в elixir у нас строки последовательно выполняются. Так что pipe, но не operator.

в точку!

ни один из языков, которые тут приводят в примеры, не является pure dataflow, это все control-flow с элементами dataflow

не является pure dataflow, это все control-flow с элементами dataflow

Погодите. Я правильно понимаю ситуацию? Вот я говорю, что есть Elixir c GenStage и Flow, которые позволяют реализовывать любую dataflow задачу стабильно, со всеми нужными примочками, обвязками и интроспекцией.

А вы говорите, что этого недостаточно, только потому что он не принуждает вас насильно писать в dataflow-подходе абсолютно всё, включая расчёт чисел Фибоначчи и вывод в консоль? Мне кажется, тут идея идёт уже в ущерб практичности и универсальности.

Эти языки не универсальные. Их практические применения, по крайней мере промышленно освоенные, это средства визуального проектирования алгоритмов обработки сигналов, систем управления и тепе. Причём с 90х эти системы ушли от текстового ЯП к графическим средам. Матлабовский Симулинк это пример такого графического решения.
Раньше много статей на эту тему было в проектировании аппаратуры - к примеру можно было создать какую то схему обработки данных, посмотреть как работает математика, потом смаппить часть кубиков на реализацию в fpga, часть на программы на С, а потом запустить симуляцию той же схемы, где часть обработки симулируется на ПО для fpga, а часть на симуляторе для какого нибудь arm, а нереализованная часть так и работает в виде модели. Было это очень дорого, в РФ едва ли даже полные наборы таких пакетов существовали.
А так да - потоковую модель организации вычислений можно реализовать на любом ЯП

Юзкейсы Симулинка понятны. Но мне показалось, что в случае Neva речь о том, чтобы всё в таком стиле писать.

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

в elixir у нас строки последовательно выполняются

Если мы хотим последовательно, то будут последовательно, хотим параллельно - будут параллельно.

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

events
|> Task.async_stream(&handle_event/1, max_concurrency: 10)  
|> Enum.to_list()

Другими словами, когда надо - распараллеливаете, когда надо - схлопываете снова в 1 поток.

Подробнее о базовых элементах: Task, Stream
И о самом потоковом подходе: GenStage и Flow

ЯП в альфе, мы ищем контрибьюторов в первую очередь, реальные задачи на нем пока решить тяжело. Но к концу 2025 все будет. Комментарий дельный, спасибо

А чем концептуально это отличается от Lustre и Signal, к примеру? И общая проблема таких языков это физическая реализуемость выполнения программы, насколько я знаю она решена только в домене статических синхронных потоковых вычислений, даже if-ы как элемент языка уже не позволяют доказать отсутствие блокировок вычислений и ограниченность использования памяти

Sign up to leave a comment.

Other news