Обновить

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

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

var newParam = Expression.Parameter(someType); 
var newBody = ReplacingExpressionVisitor.Replace(lambda.Paramters[0], newParam, lambda.Body);

А еще она решаться может и вот так:
var newParam = Expression.Parameter(someType); 
var newBody = lambda.Body.Transform(e => e == lambda.Paramters[0] ? newParam : e);


Вроде одинаково, но гипотетически усложним задачу, мало того, что надо подменить параметр, так и прибавить единички ко всем целочисленным константам. Тут уже надо городить новые классы или даже пропускать через несколько визиторов, а мы в лямбде для Transform просто усложняем условие. Это может быть ощутимо заметно на гигантском дереве, такие иногда получаются когда запрос к базе содержит монструидальный Where, созданный динамичиски какой-то UI библиотекой.

Минус Transform в том что если вы придумываете кастомные Expression, тут уже сильно не потрансформируешь, или придется расширять метод.

Если интересна реализация, ее можно найти в библиотеке CodeJam или в нашей ORM linq2db.

В защиту данного подхода скажу так, за 5 лет мне ни разу не пришлось писать новый Visitor.

Каждый, кто хоть раз делил на ноль в коде, знает это чувство: sin(x)/x при x=0 → 1 1/(x-2) при x=2 → ∞ или ошибка 0/0 → "undefined behaviour"

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

Я назвал этот подход RICIS (Recursive Indexed Calculus of Identity and Singularity) и реализовал его на чистом C# с помощью System.Linq.Expressions.

Что получилось

C#

// Классика
sin(x)/x при x=0 → 1

// RICIS
sin(x)/x при x=0 → ∞_{(Sin(x)/x)}

Не 1. Не NaN. А индексированная форма с сохранением исходного выражения.

Ещё примеры:

  • 1/(x²-4) → Monolith { ∞_1 при x=2, ∞_1 при x=-2 }

  • (x²-25)/(x-5) → x+5 (но с сохранением истории)

  • 1/(T-t) (модель blow-up в Навье-Стоксе) → ∞_{1/(T-t)}

Как это работает

Всё построено на дереве выражений и нескольких фазах:

  • Phase 1 (Clean First) — алгебраическое сокращение

  • Phase 2 (RICIS Transform) — поиск сингулярностей и создание ∞F / ∞{F/G}

  • Monolith — когда сингулярностей несколько

Никаких внешних CAS. Только System.Linq.Expressions.

А теперь главный вопрос

Это просто красивая обёртка над 0/0 или действительно новый способ работать с сингулярностями?

Я утверждаю, что RICIS позволяет решать задачи, где классическая математика и традиционные CAS пасуют или теряют информацию.

Но я могу и ошибаться.

Что думаете вы?

  • Это полезный инструмент или очередной математический велосипед?

  • Стоит ли сохранять структуру выражения при сингулярностях?

  • Есть ли смысл в индексированных бесконечностях?

Особенно интересно мнение тех, кто работает с моделированием, управлением, физикой и компьютерной алгеброй.

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

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

Публикации