Сделал документацию для своего проекта: ObservableComputations.
Также добавил некоторый новые функции и возможности.
Вкратце: ObservableComputations это библиотека, которая позволяет производить вычисления над INotifyPropertyChanged и INotifyColectionChanged объектами. Результаты вычислений также INotifyPropertyChanged и INotifyColectionChanged объекты. Вычисления включают в себя те же вычисления, что и в знакомом многим LINQ. Дополнительно можно следить за значением произвольного выражения. Вычисления реализованы как extention методы, как и в LINQ. Вызывать extention методы можно цепочкой, поддерживаются вложенные "запросы", всё как и в LINQ. Библиотека позволяет реализовать парадигму реактивного программирования.
1) В английском не силён, буду рад если кто-нибудь проверит и подредактирует документацию.
2) Приветствуются замечания, дополнения к документации, функциональности и коду.
3) Русский вариант документации хочу выложить на https://habr.com/ru/. Что посоветуете?
4) Какие фичи нужно добавить?
5) Чего не хватает для лучшего продвижения проекта?
6) Какой Вы видите область применения библиотеки, кроме той, которая описана в readme?
7) Знаете ли Вы эквивалентные по функциональности библиотеки на.NET и на других языках?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
IB>Это библиотека, которая позволяет производить вычисления над INotifyPropertyChanged и INotifyColectionChanged объектами. Результаты вычислений также INotifyPropertyChanged и INotifyColectionChanged объекты. Вычисления включают в себя тоже вычленения, что и в знакомом многим LINQ. Дополнительно можно следить за значением произвольного выражения. Вычисления реализованы как extention методы, как и в LINQ. Вызывать extention методы можно цепочкой, поддерживаются вложенные "запросы", всё как и в LINQ. Библиотека позволяет реализовать парадигму реактивного программирования.
Не понял, зачем оно может быть кому-то нужно. На github тоже как-то не написано... Или не по глазам?
Можешь пояснить, зачем кому-то может понадобиться "реализовывать парадигму реактивного программирования" вообще, и с использованием твоей библиотеки в частности?
bnk>Не понял, зачем оно может быть кому-то нужно. На github тоже как-то не написано... Или не по глазам?
Написано в разделе Use cases and benefits
bnk>Можешь пояснить, зачем кому-то может понадобиться "реализовывать парадигму реактивного программирования" вообще, и с использованием твоей библиотеки в частности? bnk>В общем, маркетинг как-то отсутствует?
Материалов в сети полно по функциональному и реактивному программированию, кто знаком с ними и с применением интерфейсов INotifyPropertyChanged и INotifyColectionChanged в WPF и Xamarin, тот сразу поймёт как можно применить библиотеку. С другой стороны в будущем возможно сделаю приложение демонстрирующее преимущества подхода.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
bnk>>Не понял, зачем оно может быть кому-то нужно. На github тоже как-то не написано... Или не по глазам?
IB>Написано в разделе Use cases and benefits
Ага, и правда просмотрел, извиняюсь. Внушает
Что касается меня, я как-то опасаюсь фреймворков, если они не написаны гигантами типа Microsoft, Google или Facebook.
Как в старой байке — "что делают программисты, когда собираются вместе? Пишут фреймворк"
IMHO, свои фреймворки не нужны, оставьте это большим дядькам.
bnk>Ага, и правда просмотрел, извиняюсь. Внушает bnk>Что касается меня, я как-то опасаюсь фреймворков, если они не написаны гигантами типа Microsoft, Google или Facebook.
Код хорошо покрыт юнит тестами.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
bnk>>Ага, и правда просмотрел, извиняюсь. Внушает bnk>>Что касается меня, я как-то опасаюсь фреймворков, если они не написаны гигантами типа Microsoft, Google или Facebook.
IB>Код хорошо покрыт юнит тестами.
Да хоть шоколадом
IMHO, для фреймворка важно доверие к производителю, документация, комьюнити, и поддержка...
Замечания по английскому
1. В реактивном программировании обычно вычисленные значения называются computed values. В mobx (кстати, JS библеотека для reactive programming) это также называется derived value. Calculation обычно вязанно с числами, что сбивает с толку.
2. Исполозование ing в именах режет слух, так как это предполагает что-то происходит прямо сейчас. Calculating<> — что-то считается сейчас. Подходящее имя для состояния, но не для сущности. Некоторые слова просто не существуют, типа Distincting, Excepting.
3. Complicity -> Complexity?
Дизайн:
Заявления про функциоанальный стиль не подтверждаются примерами. Например discountedPriceExpression не является чистой функцией. Правильнее было бы требовать от пользователя функцию Func<Order, decimal> и вызывать её когда нужно. Функциоанальный стиль так же предполагает работу с, или как минимум хорошую поддержку, immutable data structures. Было бы хорошо, если бы можно было сделать Order immutable, иметь для него какой-то observable контейнер, применить к нему Func<Order, decimal>, и получить другой observable контейнер.
Производительность:
Хорошо бы указать модель change propagation. Как обрабатываются ромбы в графе зависимостей: вершина будут пересчитана онин раз или несколько раз? Пересчитываются ли те узлы в графе, которые никто не читает? Строит ли фреймворк DAG обновлений, чтобы обрабатывать ситуацию https://en.wikipedia.org/wiki/File:Reactive_programming_glitches.svg (см. wiki раздел "glitches"), или глюки — забота пользователя фреймворка?
RX и OC имеют разную функциональность. Сравнение не имеет смысла. В RX аргументами функций (Select, Where) являются события. Причем не важно какие события.
В OC аргументами функций являются объекты реализующие INotifyPropertyChanged и INotifyCollectionChanged. OC нацелен на конкретные события: PropertyChanged и CollectionChanged. ОС кроме проброса событий PropertyChanged и CollectionChanged делает их обработку, а именно актуализирует состояние вычисляемых коллекций и объектов. При обработке событий RX играет более пассивную роль.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
IB>Выложил на GitHub и NuGet свой проект: ObservableСalculations.
IB>Вкратце: IB>Это библиотека, которая позволяет производить вычисления над INotifyPropertyChanged и INotifyColectionChanged объектами. Результаты вычислений также INotifyPropertyChanged и INotifyColectionChanged объекты. Вычисления включают в себя те же вычисления, что и в знакомом многим LINQ. Дополнительно можно следить за значением произвольного выражения. Вычисления реализованы как extention методы, как и в LINQ. Вызывать extention методы можно цепочкой, поддерживаются вложенные "запросы", всё как и в LINQ. Библиотека позволяет реализовать парадигму реактивного программирования.
IB>Документация с примерами кода есть в readme на GitHub, но ещё не дописана.
IB>1) В английском не силён, буду рад если кто-нибудь проверит и подредактирует документацию. IB>2) Приветствуются замечания, дополнения. IB>3) Какие фичи нужно добавить? IB>4) Чего не хватает для лучшего продвижения проекта? IB>5) Какой Вы видите область применения библиотеки, кроме той, которая описана в readme? IB>6) Знаете ли Вы эквивалентные по функциональности библеотеки на.NET и на других языках?
Крутая идея.
А почему не сделать собственно Linq?
Я имею в виду — примерно так:
static void Main(string[] args)
{
var orders = new ObservableCollection<Order>()
{
new Order(1, 15),
new Order(2, 15),
new Order(3, 25),
new Order(4, 27),
new Order(5, 30),
new Order(6, 75),
new Order(7, 80),
});
//********************************************
// We start using ObservableCalculations here!var expensiveOrders = from o in orders where o.Price > 25 select o;
checkFiltering(orders, expensiveOrders);
expensiveOrders.CollectionChanged += (sender, eventArgs) =>
{
// see the changes (add, remove, replace, move, reset) here
};
// Start the changing...
orders.Add(new Order(8, 30));
orders.Add(new Order(9, 10));
orders[0].Price = 60;
orders[4].Price = 10;
orders.Move(5, 1);
orders[1] = new Order(10, 17);
checkFiltering(orders, expensiveOrders); // Prints "True"
И, соответственно,
var discountedOrders = from o in orders select new {o.Num, o.Price, o.Discount, DiscountedPrice = o.Price - o.Price * o.Discount / 100};
В текущем виде как-то оно многословно выходит.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
SS>Замечания по английскому SS>1. В реактивном программировании обычно вычисленные значения называются computed values. В mobx (кстати, JS библеотека для reactive programming) это также называется derived value. Calculation обычно вязанно с числами, что сбивает с толку.
Спасибо за замечание. Скорей всего буду переименовать.
SS>2. Исполозование ing в именах режет слух, так как это предполагает что-то происходит прямо сейчас. Calculating<> — что-то считается сейчас. Подходящее имя для состояния, но не для сущности. Некоторые слова просто не существуют, типа Distincting, Excepting.
Окончанием ing я хотел подчеркнуть непрерывность и продолжительность. То есть Select — это выбрать. Выбрали и действие на этом закончено. У меня выборка происходит непрерывно, в том числе сейчас. Для сущности можно считать Selecting — герундием. Да, Distincting, Excepting отсутсвуют в английском, но можно рассматривать это как сокращения или словообразованием. Я не знаю как назвать коротко сохранив суть.
SS>3. Complicity -> Complexity?
Да, спасибо
SS>Дизайн: SS>Заявления про функциоанальный стиль не подтверждаются примерами. Например discountedPriceExpression не является чистой функцией. Правильнее было бы требовать от пользователя функцию Func<Order, decimal> и вызывать её когда нужно.
Можно и с Func<Order, decimal>. Тогда код будет таким:
Expression<Func<Order, decimal>> discountedPriceExpression =
o => o.Price - o.Price * o.Discount / 100;
//********************************************
// We start using ObservableCalculations here!
Calculating<decimal> discountedPriceCalculating =
order.Using(discountedPriceExpression);
Вместо Calculating, используем Using.
SS>Функциоанальный стиль так же предполагает работу с, или как минимум хорошую поддержку, immutable data structures. Было бы хорошо, если бы можно было сделать Order immutable, иметь для него какой-то observable контейнер, применить к нему Func<Order, decimal>, и получить другой observable контейнер.
Вся идея как раз и заключается чтобы следить за изменениями в изменяемых структурах данных. OC можно рассматривать как мост между императивным и функциональным подходами.
SS>Производительность: SS>Как обрабатываются ромбы в графе зависимостей: вершина будут пересчитана онин раз или несколько раз? Пересчитываются ли те узлы в графе, которые никто не читает? Строит ли фреймворк DAG обновлений, чтобы обрабатывать ситуацию https://en.wikipedia.org/wiki/File:Reactive_programming_glitches.svg (см. wiki раздел "glitches"), или глюки — забота пользователя фреймворка?
Делаем эксперимент
using System;
using System.ComponentModel;
using IBCode.ObservableCalculations;
namespace ObservableCalculationsExamples
{
public class ObservableTimer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _seconds;
public int Seconds
{
get => _seconds;
set
{
_seconds = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Seconds)));
}
}
}
class Program
{
static void Main(string[] args)
{
ObservableTimer observableTimer = new ObservableTimer();
Calculating<int> t = Expr.Is(() => observableTimer.Seconds + 1).Calculating();
Calculating<bool> g = Expr.Is(() => t.Value > observableTimer.Seconds).Calculating();
g.PropertyChanged += (sender, eventArgs) =>
{
if (eventArgs.PropertyName == nameof(Calculating<bool>.Value))
Console.WriteLine(g.Value);
};
observableTimer.Seconds++;
observableTimer.Seconds++;
Console.ReadLine();
}
}
}
На выходе
True
True
True
True
observableTimer.Seconds меняли 2 раза, но событие изменения g пришло 4 раза,
потому что при одном изменении observableTimer.Seconds меняется, первое: он сам, второе: меняется t.
В принципе изменения на одно и тоже значение можно подавить в клиентском коде, либо можно создать ещё один тип вычислений: DistinctiveCalculating с возможностью передавать в него EqualityComparer. Наверное реализую в будующем, это не сложно.
Но вернёмся к примеру. Здесь мы получаем везде true так как событие PropertyChanged от observableTimer первым обрабатывается в t, потом уже в g. В C# порядок вызова обработчиков совпадает с порядком подписки. Но это деталь реализации и полагаться на это в общем случае нельзя. Это проблема для OC. Если порядок обработки событий изменится то мы можем получить:
False
True
False
True
Но последнее изменение будет в любом случае True. То есть возможно временное невалидное состояние, которое в конечном итоге переходит в валидное.
Отступление: OC не застрахован временных невалидных состояний, так как одно изменение коллекции источника может породить несколько изменений вычисляемой коллекции. Например, если в коллекции источнике для Ordering произошёл Replace, то в Ordering сначала происходит Replace, а потом Move. Очевидно, что после Replace и до Move, коллекция Ordering будет упорядочена неправильно, но Move восстанавит порядок. После Replace свойство Ordering.Consistent станет = false. После Move Ordering.Consistent станет = true; Это модель временной инконсистентности.
ThenOrdering вообще может отвалиться, если одно и тоже свойство, одного и того же объекта используется в ключе для Ordering и для ThenOrdering и порядок вызова обработчиков будет отличен от порядка подписки на событие изменения свойства. Эту проблему с ThenOrdering пока не решил, но случай очень экзотитческий.
SS>Хорошо бы указать модель change propagation.
Модель change propagation: push. Спасибо, важное замечание для документации.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
S>А почему не сделать собственно Linq? S>Я имею в виду — примерно так: S>
S>static void Main(string[] args)
S>{
S> var orders = new ObservableCollection<Order>()
S> {
S> new Order(1, 15),
S> new Order(2, 15),
S> new Order(3, 25),
S> new Order(4, 27),
S> new Order(5, 30),
S> new Order(6, 75),
S> new Order(7, 80),
S> });
S> //********************************************
S> // We start using ObservableCalculations here!
S> var expensiveOrders = from o in orders where o.Price > 25 select o;
S> checkFiltering(orders, expensiveOrders);
S> expensiveOrders.CollectionChanged += (sender, eventArgs) =>
S> {
S> // see the changes (add, remove, replace, move, reset) here
S> };
S> // Start the changing...
S> orders.Add(new Order(8, 30));
S> orders.Add(new Order(9, 10));
S> orders[0].Price = 60;
S> orders[4].Price = 10;
S> orders.Move(5, 1);
S> orders[1] = new Order(10, 17);
S> checkFiltering(orders, expensiveOrders); // Prints "True"
S>
S>И, соответственно, S>
S> var discountedOrders = from o in orders select new {o.Num, o.Price, o.Discount, DiscountedPrice = o.Price - o.Price * o.Discount / 100};
S>
S>В текущем виде как-то оно многословно выходит.
Не понимаю где в коде выше точка, в которой возможно подключить использование OC.
Я думал на тем чтобы заюзать LINQ к IQueryable, тогда были бы стандартные LINQ функции, но код был бы более громоздким:
CollectionCalculationSource<Order> orders =
new ObservableCollection<Order>(new []
{
new Order(1, 15),
new Order(2, 15),
new Order(3, 25),
new Order(4, 27),
new Order(5, 30),
new Order(6, 75),
new Order(7, 80),
}).AsObservableCalculationSource();
//********************************************
// We start using ObservableCalculations here!var expensiveOrders = (from o in orders where o.Price > 25 select o).Observe();
checkFiltering(orders, expensiveOrders); // Prints "True"
expensiveOrders.CollectionChanged += (sender, eventArgs) =>
{
// see the changes (add, remove, replace, move, reset) here
};
В принципе можно создать IQueryable wrapper для OC, но я смысла в этом не вижу.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
IB>RX и OC имеют разную функциональность. Сравнение не имеет смысла. В RX аргументами функций (Select, Where) являются события. Причем не важно какие события.
Не события, а генераторы событий, IObservable.
IB>В OC аргументами функций являются объекты реализующие INotifyPropertyChanged и INotifyCollectionChanged
Т.е. генераторы событий.
IB>. OC нацелен на конкретные события: PropertyChanged и CollectionChanged.
Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>()
IB> ОС кроме проброса событий PropertyChanged и CollectionChanged делает их обработку, а именно актуализирует состояние вычисляемых коллекций и объектов. При обработке событий RX играет более пассивную роль.
Не очень понятно о чем ты, но что конкретно мешает сделать тоже самое в рамках Rx?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
B>Не понимаю где в коде выше точка, в которой возможно подключить использование OC.
Да прямо там же.
Достаточно иметь
public static class ObservableHelper
{
public static ObservableCollection<T> Where(this ObservableCollection<T> source, Predicate<T> predicate)
public static ObservableCollection<R> Select(this ObservableCollection<T> source, Expression<Func<T, R>> selector )
}
Для практического применения кода будет побольше, но принцип — тот же самый. IB>Я думал на тем чтобы заюзать LINQ к IQueryable, тогда были бы стандартные LINQ функции, но код был бы более громоздким:
Если правильно сделать, то код будет менее громоздким. IB>В принципе можно создать IQueryable wrapper для OC, но я смысла в этом не вижу.
IQueryable тут не нужен.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
B>>Не понимаю где в коде выше точка, в которой возможно подключить использование OC. S>Да прямо там же. S>Достаточно иметь S>
S>public static class ObservableHelper
S>{
S> public static ObservableCollection<T> Where(this ObservableCollection<T> source, Predicate<T> predicate)
S> public static ObservableCollection<R> Select(this ObservableCollection<T> source, Expression<Func<T, R>> selector )
S>}
S>
S>Для практического применения кода будет побольше, но принцип — тот же самый.
Я думал над такой реализацией когда начинал библиотеку. В ней есть недостаток.
Пользователь может захотеть для ObservableCollection<T> вызвать обычный LINQ метод (Where, Select).
Параллельно использовать обычный LINQ и OC при таком подходе не получится.
Дело в том, что конечно OC вызывает накладные расходы по сравнению с обычным LINQ,
и если нет надобности, в целях повышения производительности лучше вызвать обычный LINQ.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали: IB>Я думал над такой реализацией когда начинал библиотеку. В ней есть недостаток. IB>Пользователь может захотеть для ObservableCollection<T> вызвать обычный LINQ метод (Where, Select).
Может быть. IB>Параллельно использовать обычный LINQ и OC при таком подходе не получится. IB>Дело в том, что конечно OC вызывает накладные расходы по сравнению с обычным LINQ, IB>и если нет надобности, в целях повышения производительности лучше вызвать обычный LINQ.
Хм. Имхо, стоит поточнее оценить накладные расходы. По-моему, их можно будет свести к минимуму, как в скорости, так и в памяти.
К примеру, можно вообще не подписываться на события "завёрнутой" коллекции до тех пор, пока на наши события никто не подписан.
Тогда никаких лишних вызовов производиться не будет.
А размер памяти, потребляемой "observable" результатом select или where будет ненамного больше, чем у результата обычного linq-to-objects запроса.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
S>По-моему, их можно будет свести к минимуму, как в скорости, так и в памяти.
Я над этим отдельно работал
S>К примеру, можно вообще не подписываться на события "завёрнутой" коллекции до тех пор, пока на наши события никто не подписан. S>Тогда никаких лишних вызовов производиться не будет.
Возможно пользователю не нужно подписываться на события вычисляемой коллекции. Ему просто нужно чтобы всегда "под рукой" была вычисленная коллекция. Например, если вычисление сложное и\или коллекции источники большие, каждый раз вычислять коллекцию обычным LINQ не хочется по соображениям производительности. Например, есть Dictionaring. Его можно рассматривать как самообновляемый словарь, событий у него нет. Такой выигрыш в производительности ещё один сценарий использования OC, кроме событий. Кстати забыл упомянуть этот сценарий в документации.
S>А размер памяти, потребляемой "observable" результатом select или where будет ненамного больше, чем у результата обычного linq-to-objects запроса.
То же надо оценивать.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml