2

The original post contained a problem, I managed to solve, introducing a lot of issues with shared mutable state. Now, I'm wondering, if it can be done in a pure functional way.


Requests can be processed in a certain order.

For each order i there is an effectiveness E(i)

Processing request should follow three conditions

  1. There should be no delay between acquiring the first request and processing it

  2. There should be no delay between processing some request and processing next request

  3. When there are several orders of processing requests, the one with highest effectiveness should be chosen


Concrete example:

For an infinite list of integers, print them, so, that prime numbers are generally earlier, than not prime numbers

Effectiveness of ordering is reverse to the number of times we had primes in queue, but printed non prime


My first solution in C# (not for primes, obviously) used some classes having a shared mutable state represented by a concurrent priority queue. It was ugly, because I had to manually subscribe classes to events and unsubscribe them, check that queue is not exhausted by one intermediate consumer before other consumer processes it and etc.

To refactor it, I chose Reactive Extensions library, which seemed to address issues with state. I understood that in the following case I couldn't use it:


The source function accepts nothing and returns IObservable<Request>

The process function accepts IObservable<Request> and returns nothing

I have to write a reorder function, which reorders requests on their way from source to process.

Internally reorder has a ConcurrentPriorityQueue of orders. It should handle two scenarios:

  1. When process is busy with processing reorder finds better orderings and updates the queue

  2. When process has requested a new order reorder returns the first element from queue


The problem was that if reorder returned IObservable<Request>, it wass unaware, whether items were requested from it, or no.

If reorder had called OnNext immediately upon receiving, it did not reorder anything and violated condition 3.

If it ensured, that it had found the best ordering, it violated conditions 1&2 because process could become idle.

If reorder returned ISubject<Request>, it exposed an option to call OnError and OnCompleted to consumer.

If reorder has returned the queue, I would have returned to where I started


The problem was that cold IObservable.Create was not lazy enough. It started exhausting queue with all requests when a subscription to it was made but results of only the first ones were used.

The solution I came up with is to return observable of requests, i.e. IObservable<Func<Task<int>>> instead of IObservable<int>

It works when there is only one subscriber, but if there are more requests used, than there are numbers generated by source, they will be awaited forever.

This issue can probably be solved by introducing caching, but then consumer which consumed a queue fast will have side effects on all other consumers, because he will freeze the queue in less effective ordering, than it would be after some waiting.

So, I will post solution to the original question, but It's not really a valuable answer, because it introduces a lot of problems.

This demonstrates why doesn't functional reactive programming and side effects mix well. On the other hand, it seems I now have an example of a practical problem impossible to solve in pure functional way. Or don't I? If Order function accepted optimizationLevel as a parameter it would be pure. Can we somehow implicitly convert time to optimizationLevel to make this pure as well?

I'd like to see such solution very much. In C# or any other language.


Problematic solution. Uses ConcurrentPriorityQueue from this repo.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Reactive.Linq;
using DataStructures;
using System.Threading;

namespace LazyObservable
{
    class Program
    {
        /// <summary>
        /// Compares tuple by second element, then by first in reverse
        /// </summary>
        class PriorityComparer<TElement, TPriority> : IComparer<Tuple<TElement, TPriority>>
            where TPriority : IComparable<TPriority>
        {
            Func<TElement, TElement, int> fallbackComparer;
            public PriorityComparer(IComparer<TElement> comparer=null)
            {
                if (comparer != null)
                {
                    fallbackComparer = comparer.Compare;
                }
                else if (typeof(IComparable<TElement>).IsAssignableFrom(typeof(TElement))
                    || typeof(IComparable).IsAssignableFrom(typeof(TElement)))
                {
                    fallbackComparer = (a,b)=>-Comparer<TElement>.Default.Compare(a,b);
                }
                else
                {
                    fallbackComparer = (_1,_2) => 0;
                }
            }
            public int Compare(Tuple<TElement, TPriority> x, Tuple<TElement, TPriority> y)
            {
                if (x == null && y == null)
                {
                    return 0;
                }
                if (x == null || y == null)
                {
                    return x == null ? -1 : 1;
                }
                int res=x.Item2.CompareTo(y.Item2);
                if (res == 0)
                {
                    res = fallbackComparer(x.Item1,y.Item1);
                }
                return res;
            }
        };
        const int N = 100;
        static IObservable<int> Source()
        {
            return Observable.Interval(TimeSpan.FromMilliseconds(1))
                .Select(x => (int)x)
                .Where(x => x <= 100);
        }
        static bool IsPrime(int x)
        {
            if (x <= 1)
            {
                return false;
            }
            if (x == 2)
            {
                return true;
            }
            int limit = ((int)Math.Sqrt(x)) + 1;
            for (int i = 2; i < limit; ++i)
            {
                if (x % i == 0)
                {
                    return false;
                }
            }
            return true;
        }
        static IObservable<Func<Task<int>>> Order(IObservable<int> numbers)
        {
            ConcurrentPriorityQueue<Tuple<int, int>> queue = new ConcurrentPriorityQueue<Tuple<int, int>>(new PriorityComparer<int, int>());
            numbers.Subscribe(x =>
            {
                queue.Add(new Tuple<int, int>(x, 0));
            });
            numbers
                .ForEachAsync(x=>
                {
                    Console.WriteLine("Testing {0}", x);
                    if (IsPrime(x))
                    {
                        if (queue.Remove(new Tuple<int, int>(x, 0)))
                        {
                            Console.WriteLine("Accelerated {0}", x);
                            queue.Add(new Tuple<int, int>(x, 1));
                        }
                    }
                });
            Func<Task<int>> requestElement = async () =>
              {
                  while (queue.Count == 0)
                  {
                      await Task.Delay(30);
                  }
                  return queue.Take().Item1;
              };
            return numbers.Select(_=>requestElement);
        }
        static void Process(IObservable<Func<Task<int>>> numbers)
        {
            numbers
                .Subscribe(async x=>
                {
                    await Task.Delay(1000);
                    Console.WriteLine(await x());
                });
        }

        static void Main(string[] args)
        {
            Console.WriteLine("init");
            Process(Order(Source()));
            //Process(Source());
            Console.WriteLine("called");
            Console.ReadLine();
        }
    }
}
user2136963
  • 2,526
  • 3
  • 22
  • 41

1 Answers1

0

To summarize (conceptually):

  1. You have requests that come in irregularly (from source), and a single processor (function process) that can handle them.
  2. The processor should have no downtime.
  3. You're implicitly going to need some sort of queue-ish collection to manage the case where the requests come in faster than the processor can process.
  4. In the event that there are multiple requests queued up, ideally, you should order them by some effectiveness function, however the re-ordering shouldn't be the cause of downtime. (Function reorder).

Is all this correct?

Assuming it is, the source can be of type IObservable<Request>, sounds fine. reorder though sounds like it should really return an IEnumerable<Request>: process wants to be working on a pull-basis: It wants to pull the highest priority request once it frees up, and wait for the next request if the queue is empty but start immediately. That sounds like a task for IEnumerable, not IObservable.

public IObservable<Request> requester();
public IEnumerable<Request> reorder(IObservable<Request> requester);
public void process(IEnumerable<Request> requestEnumerable);
Shlomo
  • 14,102
  • 3
  • 28
  • 43
  • It's correct. But when I create an `IEnumerable`, somewhere in it there is a line `yield return something();`. In case, `something()` is `getValueWithHighestPriorityAfterCalculatingAllPriorities()` I will have downtime. In case `something()` is `getFirstValueFromQueue()` aka `getValueWithHighestPriorityBeforeCalculatingAllPriorities()` I don't have reordering. – user2136963 Jan 21 '16 at 16:37
  • You would probably have to implement the `IEnumerable` as a class. I'm mocking something up. – Shlomo Jan 21 '16 at 16:59