Информация об изменениях

Сообщение Re[2]: Observable calculations от 17.11.2019 16:34

Изменено 17.11.2019 16:47 igor-booch

Re[2]: Observable calculations
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>Хорошо бы указать модель change propagation. Как обрабатываются ромбы в графе зависимостей: вершина будут пересчитана онин раз или несколько раз? Пересчитываются ли те узлы в графе, которые никто не читает? Строит ли фреймворк 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 пока не решил, но случай очень экзотитческий.
Re[2]: Observable calculations
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>Хорошо бы указать модель change propagation. Как обрабатываются ромбы в графе зависимостей: вершина будут пересчитана онин раз или несколько раз? Пересчитываются ли те узлы в графе, которые никто не читает? Строит ли фреймворк 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 пока не решил, но случай очень экзотитческий.