HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Calculation caching experiment

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
experimentcachingcalculation

Problem

There are a lot of numerical properties to be invoked many times with an expensive calculation. Let’s say for the sake of example, they expose a Factorial:

public class Analysis
{
    public double n10 => F(10);
    public double n100 => F(100);
    double F(int n) => 
        n <= 1 ? 1 : n * F(n-1);
}


I was looking for a way to cache them while keeping syntax clean, so property calculation logic will not intermix with caching. A proposed solution is:

public class Analysis
{
    public double n10 => this.Cached() || F(10);
    public double n100 => this.Cached() || F(100);
    double F(int n) => 
        n <= 1 ? 1 : n * F(n-1);
}


Where library code is:

public static class Caching
{
    static ConditionalWeakTable> Values { get; } =
        new ConditionalWeakTable>();

    public static Value Cached(this object target, [CallerMemberName] string name = null) =>
        Values.GetOrCreateValue(target).ContainsKey(name) ?
            new Value(() => Values.GetOrCreateValue(target)[name], null) :
            new Value(null, v => Values.GetOrCreateValue(target)[name] = v);        
}


And:

public class Value
{
    public static implicit operator Value(double value) => new Value(() => value, null);
    public static implicit operator double(Value value) => value.Getter();
    public static bool operator true(Value value) => value.Getter != null;
    public static bool operator false(Value value) => value.Getter == null;

    public static Value operator |(Value cached, Value computed)
    {
        cached.Setter(computed);
        return computed;
    }

    public Value(Func getter, Action setter)
    {
        Getter = getter;
        Setter = setter;
    }

    Func Getter { get; }
    Action Setter { get; }
}

Solution

Operator-free version (answer to...your answer):

public static class Analysis
{
    public static readonly Lazy n10 = new Lazy(() => F(10));

    private double static F(int n)
    {
        return n <= 1 ? 1 : n * F(n - 1);
    }
}


Do not look at marginal style differences (I cannot get used/appreciate expression bodied functions but it's just my own problem), point I'd like to highlight is:

  • You do not need Once class, Framework already has a class for lazy creation of values: Lazy. It even nicely handle thread-safety issues (safe creation and/or publication) if field is not static (in that case C# itself helps us.)



Few minor notes about current code:

  • Analysis class has no need to be instantiable and all methods can be static.



  • Class itself can then be static.



  • IMO you do not need a dictionary, first of all because of the performance impact (even if it's a concurrent collection without locks) but also because it limits its usage to static methods (you use MethodInfo as key).



I understand your point of view (C# syntax isn't so clean to be used for business logic) but instead of forcing syntax and change operators semantic I'd prefer to use another language. Boo was first that came to my mind (especially because to change and extend the language to have a DSL is incredibly easy). I used it few years ago then I'm not up-to-date with its evolution. There are also other tools (second thought if for Microsoft Modeling SDK if you like visual editors instead of plain text code)

Code Snippets

public static class Analysis
{
    public static readonly Lazy<double> n10 = new Lazy<double>(() => F(10));

    private double static F(int n)
    {
        return n <= 1 ? 1 : n * F(n - 1);
    }
}

Context

StackExchange Code Review Q#141756, answer score: 4

Revisions (0)

No revisions yet.