Как стать автором
Обновить

Комментарии 34

Почему вы не использовали стандартный компилятор C#? Зачем такие сложности?

Скорость компиляции

Компилятор C# компилирует C#. Здесь же грамматика явно отличается.

Я понял что отличается, но зачем это, если как раз можно использовать и стандартный C# и не писать компилятор...

Потому что скрипт задает пользователь, а не программист хардкодит его в проект.

Нельзя сувать непроверенный ввод пользователя в компилятор C#. А проверка ввода тут сама по себе по сложности как компиляция.

C# и F# не подходят для этих задач:

  1. Пользователям (бугалтерам, редакторам, пусконаладчикам, проектантам) очень сложно (а скорее невозможно) писать код с явной типизацией (пускай даже простой)

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

    Сравните с точки зрения не программиста эти две клинописи:

var i = new[]{1,2,3}.Select(a=>Math.Pow(a,2));
var b = new User(){ Age = 18, Name = "Kate"};
var c = i.First() >0 ? "positive" : "negative";

и

i = [1,2,3].map(rule it**2)
b = {age = 18, name = 'Kate'}
с = if(i[0]>0) 'positive' else 'negative'

  1. Скорость компиляции у C#/F# - отстой. Это критично как для embedded -сценариев,так и для одиночного запуска

  2. Кол-во звездочек у пакета Ncalc

  1. Потому что я могу написать свой мини-ЯП !

Только вот у вас во втором пункте две разные "клинописи". Ни один пример не будет понятным бухгалтеру или редактору.

Сегодня как раз на стриме будем обсуждать.

Проверяли на практике (проэктанты Scada-системы Sonica). Там был питон/С# и Nfun. 

По итогу - проектанты:

  • При касании C# - сразу говорили "Нет, мы это делать не будем, лучше на костылях".

  • На питоне плакали кололись и допускали очень много ошибок.

  • На Nfun спокойно пишут, ошибок не допускают (но лямбды не используют).

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

А по факту - действительно "идеального" решения пока нет. Но сложность например SQL дается большинству. Python уже сложнее. C# это ппц для обычных ребят. Но так как мы идем в мир где минимальное знание ЯП становится таким же естественным как умение решать простые уравнения - то это вселяет в меня оптимизм

Я точно не уверен насколько это просто, но рассматривали ли вы интеграцию с .NET Interactive как альтернативу делать свое решение? .NET Interactive поддерживает скрипты на C#, F#, PowerShell, JavaScript. Я думаю что F# и JavaScript достаточно просты. Жаль конечно что нет поддержки Python. Это сейчас один из самых популярных скриптовых языков. И мне безусловно интересно как в .NET Interactive реализованна защита песочницы, и есть ли она там...

Рассматривали ли вы Python или JavaScript как языки для написания скриптов? Их достаточно часто применяют в похожих сценариях. Я не искал готовые для этого библиотеки в .NET, но возможно они есть. Тут конечно надо серьёзно взвешивать все за и против, особенно в части передачи данных между .NET и скриптом.

По 4 пункту — если вы будете сохранять результат компиляции, то скорость компиляции C#, F# оригинальным компилятором не должна стать проблемой. Тут конечно возникает вопрос защиты песочницы. Насколько просто сделать защиту песочницы, при использовании оригинального компилятора.

По 2 пункту, если честно синтаксис спорный в том плане что он нестандартный, опять напомню про синтаксис Python или JavaScript как альтернативу придумывать свой. Ваш мне очень напоминает смесь C# и JavaScript с расширениями. Особенно нестандартно и крипово выглядит (rule it**2).

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

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

Python, JavaScript

Python, JavaScript - рассматривали. Более того - изначально на проекте был именно IronPython. На нем столкнулись с проблемами:

  • Вопросы про песочницу

  • Огромные проблемы вызванные слабой типизацией. Люди писали скрипты которые по просту ломались на объектах

  • Скорость исполнения (в сценариях где было много перезапуском сложных скриптов)

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

  • Плохой inter-op. Из за этого скрипт Python был намного более грамоздкий. Например мы не могли понять какая переменная только для чтения, а какая только для записи

  • Так же мы не могли понять какой тип может быть у переменной что опять таки приводило к ошибкам в рантайме. Еще мы не могли оценить скорость выполнения - и любой while true: мог поставить колом или просадить перфоманс в критически важной части

  • Все вышесказанное еще более справедливо для JavaScript

Скорость и прекомпиляция

Я изучал возможности использования C# и F# с прекомпиляцией. Анализ нашей и схожих проблем показал что самым критичным является именно скорость первичной компиляции или one-time-shot(build+run+get results) perfomance:

Примеры:

  • Cкорость запуска промшленного оборудования (чье железо было очень слабым)

  • На запросе вытащить сущность из бд и выполнить связанный с ней скрипт. Тут не покешируешь:(

  • Сложность интеграции и поддержки такого пакета

.Net interactive

.Net interactive - не смотрел. По результатам поверхностный осмотра - все же это разные звери для разных задач:

  • Есть вопросы про песочницу

  • Есть вопросы про скорость исполнения one-time-shot

  • JavaScript - слаботипизирован. См. ниже про python/javascript ниже

  • F# syntax - не уверен что подходит для начинающих

Но похоже нужно смотреть для кругозора

Rule-syntax для anonymous-hi-order-functions

Это очень интересный вопрос, с многими часами размышлений и различными прототипами. Отвечу на него развернуто.

Корневая проблема

На практике оказалось (сюрприз-сюрприз) что для не-программистов hi-order function это чудовищно сложная абстракция. Как 0 для древних людей. Люди боялись ее, и всячески избегали (не смотря на кажущуюся для нас с вами очевидность).

Почему не classic way. a->b syntax

Начиналось все с классического синтаксиса a->b, но в реальности это привело к тому, что ребята попросту отказались пользовать Nfun увидев стрелки. Это когнитивно пугающий синтаксис и они готовы были писать огромные скрипты python просто испугавшись, как они выразились "высшей математики на ровном месте". Если описывать эмоционально, то кажется что пользователи начали бояться заодно и обычной арифметики в nfun ибо бог знает что за этим скрывается, и бог знает на каком шаге придется открывать учебник высшей математики. То есть сам факт наличия оператора стрелки в выражении превращал "тупо выражение" в "шайтан заклинание"

Хотя поиграться со стрелками и всеми примерами вида

y = a->b->c->a+b+c

было очень приятно. Было очень больно выкорчевывать этот синтаксис, когда он уже был разобран и работал.

Почему не kotlin-way. map {it*it} syntax

Альтернативой был синтаксис котлина, с хвостовыми лямдами вида

map{ it*it }

Этот синтаксис не вызывал вопросов у людей, но встал вопрос о консистентности.

Например назревал конфликт с синтаксисом анонимных структур

a={age=42, name ='vasa'}.

Анонимные структуры для nfun намного важнее лямбд, потому им отдается приоритет. С другой стороны введение блока {} логично для многострочных выражений и так же требует использовать {} для всех блоков кода ради консистентности, а это сразу уход в сторону C-Like языков, чего мне хотелось избежать (так как, опять таки, это разумно для более сложного и профессионального применения, а для бытового языка - может напугать.

Был еще вариант синтаксиса map @{it*it}но тут вставал вопрос консистентности - ибо "откуда тут @ ?"

Почему не python-way. x for y syntax

Другой альтернативой был путь питона, с его x for y оператором. Однако этот оператор решает только небольшое кол-во задач с массивами, да и сам по себе. достаточно своеобразен.

В отсутствии циклов, многострочных statements и изменяемых переменных - ограниченность этого подхода становится критичной. Нужно либо делать из nfun полноценный Яп (что конечно круто, но не понятны трудозатраты/сферы применения) либо придумывать новые синтаксисы для различных linq - задач (встраивать Sql/ C#linq?) что очень усложняет nfun. Все же хочется использовать единый подход для большого скоупа задач

Почему rule

Нужно было найти понятную абстракцию для функций высших порядков. Так и появился rule.

Теперь можно объяснить этот синтаксис примерно так:

# rule - это правило применяемое к каждому элементу в списке. 
# с его помощью, например, мы можем взять только те элементы списка, 
# которые удовлетворяют заданному правилу
           
[1,2,3,4,5].filter(rule it>3) #[4,5]

# в примере выше, мы взяли только те элементы которые больше 3.
# внутри правила, 'it' - это элемент для которого применяется правило
            

Вероятно можно сформулировать это лучшим образом, но думаю идея ясна

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

В теории, строка

x = rule it-1 y = x(1)

может быть разобрана (но в nfun есть чисто косметический запрет, требующий перенос строки или ; между выражениями), при этом синтаксис аналогичен тому как мы моглибы написать

... else x-1 y = x(1)

То есть мы не вводим нового синтаксиса, а используем старые паттерны. Это сразу наводит на мысль что нужно делать try-catch syntax и так далее. Впрочем консистентность синтаксиса - слишком тонкая и спорная материя, но надеюсь я передал свое "видение"

Почему ключевое слово rule а не fun

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

То есть для обычного пользователя в этом нет метафоры (которая есть у нас с вами, как людей в контексте современных трендов ЯП)

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

f(x) = x**2 - 2*x + 3

result = f(12)

Впрочем эти решения могут быть переиграны, если нужно будет делать из Nfun полноценный ЯП

Крипово или нестандартно?

Я полагаю что в данном случае нестандартно == крипово только для опытного программиста, ибо требует некоторого когнитивного напряга, ведь привычная нам метафора/синтаксис не используется, а вместо него что то незнакомое

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

Что можно переделать

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

Спасибо за отличный подробный ответ. Даже не с чем особо поспорить и предложить что-то для рассмотрения/улучшения.

Как я понял у вас синтаксис максимально упрошен и даже нет ключевого слова для определения новых функций/методов. Идентификатор() = значит новая функция. Это круто!!!

Получилось даже круче чем здесь https://github.com/Draco-lang/Language-suggestions

Там ребята проектируют замену C#. У них тоже круто получается. Синтаксис и возможности получились очень схожими и минималистическими. Выглядят оба очень потрясающе. Может что вы им сможете предложить!!! Вот их видео презентация https://youtu.be/rxbyQS-qHfg

У них полноценный язык поэтому они добавили еще метки для goto и макросы. Мне кажется они сделали макросы похожими на Rust, но я не уверен, потому что Rust не знаю. В вашем случае макросы и goto совершенно не нужны.

На запросе вытащить сущность из бд и выполнить связанный с ней скрипт. Тут не покешируешь:(

Ещё как покешируешь, можно даже перед тем как писать скрипт в базу его скомпилировать.

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

НЛО прилетело и опубликовало эту надпись здесь

Конкуренты

https://github.com/sklose/NCalc2

https://github.com/codingseb/ExpressionEvaluator

Language server

Не реализован. language server это слишком крутая задача, что бы сделать ее в стол.

Но вероятно стоит накидать подсветку синатксиса, ибо для этого достаточно подсветить ключеве слова, операторы и паттерны видаy=... и f(.... Это кажется разумным реквестом

Настраиваемость фич

Пример

Настраиваются:

  • Запрет if

  • Тип целых чисел по умочанию

  • decimal или float арифметика

  • integer overflow

  • Запрет пользовательских функций

  • Запрет рекурсивных функций

  • Список функций

  • Список констант

Вероятно можно ввести множество других флагов типа

  • запрет функций высших порядков

  • запрет списков

  • запрет строк /интерполяции строк

Тут нужны примеры использования что бы понять что именно и где запрещать/расширять ибо здесь у меня нету строгого ощущения что нужно людям

Ошибки

Я постарался разобрать как можно большее кол-во ошибок, с понятным объяснением "что не так".

Разобрано около 140 различных возможных ошибок

Пример: https://github.com/tmteam/NFun/blob/master/src/NFun/ParseErrors/Errors.3.Syntax.cs

Полный список ошибок : https://github.com/tmteam/NFun/tree/master/src/NFun/ParseErrors

В последнее время в большинстве статей по C# идет описание "Как это сделать"
Но я почти всегда перестаю понимать "Зачем это делать"?

Так и здесь - объясните зачем это делать?
Можете описать прикладное пример этого?

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

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

  • Запись выражений в скада системах и или лоукод решениях (смотрите скриншоты в статье)

  • Хранение выражений вместо констант в конфигах (пример с Settings.json в статье). Можно сказать что это сериализация лямбд.

  • Web UI - например фильтры запросов, правила начисления бонусов и так далее (скриншоты в статье)

  • Тонкая настройка фильтров изображений

  • Верстка, в том числе email. Например если раньше вы писали

Дорого пользователь. Ваш баланс низкий и скоро мы вас заболим

то теперь можно писать:

{if (user.gender==m} 'Дорогой' else 'Дорогая'} {user.name}. Ваш баланс равен {user.balance} $ и скоро мы вас заблочим

Раньше для такого нужно было напрягать программистов. Теперь это можно писать просто в вашей CRM

Это примеры которые были в практике или сходу пришли мне в голову.

Примеры:

Можете попробовать перевести слово "инвестигация" на русский, это не сложно.

Это действительно не сложно - но у этих слов сейчас различные оттенки:

  • Исследования - это что то связаное с формальной наукой. "Делал дома исследования" - звучит кустарно

  • Изучение - это больше про учебу и не смотрится в оригинальном тексте

  • Инвестигации - это именно кустарное, айтишное исследование

А так как слово "Инвестигация" - прекрасно склоняется и встраивается в язык - не вижу проблем. Мы же не боимся слова "инфа" или "комп" чьи корени так же заимствованы.

Процесс обогащения языка заимствованными корнями, при условии склоняемости слов - это не плохо. Даже хорошо

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

Инвестигатор (investigator) - это полностью заимствованное понятие, соответственно и парное с ним тоже прямо заимствуется

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

А ведь есть достаточно точный перевод - расследование, следователь. Это у нас оно с оттенком криминала и журналистики, но, ведь, можно пробовать использовать и в этом контексте. Мне показалось, что страны со скудным языком пытаются выдумать свои слова, а с богатым - заимствовать. В крайностях напоминает некую Зою Вексельштейн с Ютюба :)

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

Нужна подсветка синтаксиса или хотя бы минимальный валидатор на js.

Можно бахнуть отдельный парсер (создать отдельный диалект), например в синтаксисе gherkin если это имеет смысл. Тут нужно изучать, но это любопытно

Соглашусь про подсветку синтаксиса. Для этого достаточно подсветить ключеве слова, операторы и паттерны видаy=... и  f(...,а вот про валидатор интереснее - что именно вы имеете ввиду под этим?

Я искал вариант с минимум велосипедостроения, так как не хотелось избыточной сложности на ровном месте. Для gherkin есть реализации на чистом js, что дает возможность подключить его прямо на фронте и сразу выдавать сообщения об ошибках компиляции.

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

Полноценный валидатор примерно равен по сложности самому Nfun, так как требует написания системы выведения типов. Это можно сделать но только после полной формализации оригинальной системы выведения.

Однако можно сделать упрощенный валидатор - проверка только синтаксиса, и некоторой очевидной семантики. Его должно хватить, но все равно валидация синтаксиса в этом случае будет двух ступенчатой: 1) проверка на фронте, 2) отослать на бек финально завалидировать. Пока не знаю как сделать лучше

Снова подкинули задачу на связанную тему, и вот что мне пришло в голову, почему бы не использовать эксел-подобные формулы (туда даже лямбды завезут скоро:))?

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории