4
\$\begingroup\$

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<object, Dictionary<string, double>> Values { get; } =
        new ConditionalWeakTable<object, Dictionary<string, double>>();

    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<double> getter, Action<double> setter)
    {
        Getter = getter;
        Setter = setter;
    }

    Func<double> Getter { get; }
    Action<double> Setter { get; }
}
\$\endgroup\$
3
  • \$\begingroup\$ You really love to override operators to completely change semantic! LOL As usual I don't think it's a nice idea and...well...we already have ?? \$\endgroup\$ Sep 19, 2016 at 13:01
  • \$\begingroup\$ @AdrianoRepetti I just think about it in plain English: cached or calculated. Unfortunately, ?? is not overloadable. \$\endgroup\$ Sep 19, 2016 at 17:49
  • \$\begingroup\$ @AdrianoRepetti Operator free version :) \$\endgroup\$ Sep 19, 2016 at 19:33

3 Answers 3

4
\$\begingroup\$

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

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);
    }
}

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<T>. 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)

\$\endgroup\$
2
  • \$\begingroup\$ Yep, it is all about that nice moment when you do need to work with instantiated objects (not static classes): Lazy<T> + C# syntax inefficiency triple (!) the code amount you need to write/maintain... Sorry for + operator overloading :) \$\endgroup\$ Sep 20, 2016 at 22:34
  • \$\begingroup\$ It's nice to play with the syntax and it's especially good you wish to discuss your research with the community! I agree, sometimes C# is pretty verbose but IMO it helps on clarity...after all I'm Italian...we love to chat/write bla bla bla... \$\endgroup\$ Sep 21, 2016 at 6:54
1
\$\begingroup\$

@AdrianoRepetti Operator free version :)

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

Where:

class Once
{
    static ConditionalWeakTable<object, ConcurrentDictionary<MethodInfo, double>> Values { get; } =
        new ConditionalWeakTable<object, ConcurrentDictionary<MethodInfo, double>>();

    public static implicit operator Once(Func<double> value) => new Once(value);
    public static implicit operator double(Once once) => once.Value;

    Once(Func<double> getter)
    {
        Getter = getter;            
    }

    double Value => Values.GetOrCreateValue(Getter.Target)
        .GetOrAdd(Getter.Method, _ => Getter());

    Func<double> Getter { get; }
}

As for me - it does not look so clean.

\$\endgroup\$
3
  • \$\begingroup\$ Side question, for DSLs can't you give a chance to Boo? Long time ago (some years) I used it I had pretty nice impression \$\endgroup\$ Sep 19, 2016 at 20:30
  • \$\begingroup\$ @AdrianoRepetti I will have a look at it. Do you think that this book is still applicable? Has it changed significantly since 2010? \$\endgroup\$ Sep 19, 2016 at 20:41
  • \$\begingroup\$ Honestly I have no idea, last time I used Boo in a project is back at 2010/2011 (more or less). I loved the way you can change the language but after that I had no more chances to use it \$\endgroup\$ Sep 19, 2016 at 21:25
1
\$\begingroup\$

I find this a nice idea but not useful at all, sorry ;-)

You might as well create a decorator cache that stores the results.

A real cache should be able to cache expressions like:

var a = 2;
var b = 3;
CachedExpression.Calc(() => a * b);
CachedExpression.Calc(() => 2 * b);

where

class CachedExpression
{
    ...cache

    public static T Calc<T>(Expression<Func<T>> expr)
    {
        ...expression comparison magic, evaluation and caching
    }
}

but I doubt it will be faster then calculating the actual expression due to the C# Expression overhead.

\$\endgroup\$
1
  • \$\begingroup\$ I cannot imagine anything related to real math applications where Value Identity must be used instead of Reference Identity, as most of expressions are defined at design time and almost never duplicated, so Target+MethodInfo is good enough. I am also looking for something which works for extension methods, so it is exactly what I need. \$\endgroup\$ Sep 26, 2016 at 4:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.