Click here to Skip to main content
15,893,381 members
Articles / Programming Languages / C#

AFP: Almost Functional Programming in C#: Part 2

Rate me:
Please Sign up or sign in to vote.
4.89/5 (4 votes)
6 Mar 2013CPOL20 min read 17.5K   142   12  
Putting the programming style to the test by using it in a multi-threaded server.
#region copyright
//                Copyright Andrew Rafas 2012.
// Distributed under the Eclipse Public License, Version 1.0.
//           (See accompanying file LICENSE.txt or 
//     copy at http://www.eclipse.org/legal/epl-v10.html)
#endregion
using System;
using System.Collections.Generic;

namespace CSFP.Memoize
{
    public abstract class Dependent : IDependent, ISource
    {
        protected int _notReadyCount; // if == 0 then we can calculate this dependent, otherwise the value will be invalid
        protected bool _isCached; // can be false even if _uncachedCount == 0
        protected DependentList _dependentList;
#if DEBUG
        internal readonly ISource[] SourceList;
#endif

        protected Dependent(params ISource[] sourceList)
        {
#if DEBUG
            SourceList = sourceList;
#endif
            _notReadyCount = 0;
            _isCached = false;
            _dependentList = EmptyDependentList.Instance;

            for (int i = 0; i < sourceList.Length; ++i) {
                var d = sourceList[i];
                d.AddDependent(this, i);
                if (!d.IsReady)
                    ++_notReadyCount;
            }
        }

        void IDependent.Notify(int readyChange, int key)
        {
#if DEBUG
            Helper.Log("Notify({0}): {1} ({2} -> {3}, {4})", ++Helper.NotificationCount, this, _notReadyCount, _notReadyCount - readyChange, key);
#endif
            var oldReady = _notReadyCount == 0;
            _notReadyCount -= readyChange;
            var newReady = _notReadyCount == 0;
            // if !_isCached then all dependencies, and all their dependencies, and so forth have !IsCached...
            // if oldReady == newReady then we do not have to do antyhing to keep this invariant...
            if (oldReady != newReady) {
                _isCached = false; // ready change implies invalidation
                _dependentList.NotifyAll(newReady ? DependentList.BecomeReady : DependentList.BecomeNotReady);
            } else if (_isCached) {
                _isCached = false;
                _dependentList.NotifyAll(DependentList.ValueChanged);
            }
        }

        bool ISource.IsReady
        {
            get { return _notReadyCount == 0; }
        }

        void ISource.AddDependent(IDependent dependency, int key)
        {
            _dependentList = _dependentList.Add(new DepKey(dependency, key));
        }
    }

    public abstract class FunctionBase<R> : Dependent, IValue<R>
    {
        protected R _cachedValue;

        protected FunctionBase(params ISource[] sourceList)
            : base(sourceList)
        {
        }

#if DEBUG
        [System.Diagnostics.Conditional("DEBUG")]
        void LogCall()
        {
            var sb = new System.Text.StringBuilder();
            for (int i = 0; i < SourceList.Length; ++i) {
                if (i != 0)
                    sb.Append(", ");
                object value = "?";
                sb.Append(SourceList[i].ToString());
            }
            Helper.Log("Calculate({0}): {1} ({2})", ++Helper.CalculationCount, this, sb.ToString());
        }
#endif

        public override string ToString()
        {
            return base._isCached ? _cachedValue.ToString() : "?";
        }

        protected abstract void UpdateCached();

        public R Value
        {
            get
            {
                if (base._isCached)
                    return _cachedValue;
                // We do not check IsReady, if the caller was so dumb to call us when we are not ready, 
                // then we will get an exception higher up in the function call chain.
#if DEBUG
                LogCall();
#endif
                UpdateCached();
                base._isCached = true;
#if DEBUG
                Helper.Log("Result: {0} := {1}", this, _cachedValue);
#endif
                // The prior call will cause ValueChanged notifications if some value gets cached 
                // higher up in the function call chain, but since we are most likely called from a 
                // function below us, the notification will not propagate down as they are most 
                // likely !_isCached. (It can propagate though if we fan out downwards...)
                base._dependentList.NotifyAll(DependentList.ValueChanged);
                return _cachedValue;
            }
        }
    }

    public sealed class Function<T, R> : FunctionBase<R>
    {
        IValue<T> _param;
        Func<T, R> _func;

        public Function(IValue<T> param, Func<T, R> pureFunction)
            : base(param)
        {
            _param = param;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param.Value);
        }
    }

    public sealed class Function<T1, T2, R> : FunctionBase<R>
    {
        IValue<T1> _param1;
        IValue<T2> _param2;
        Func<T1, T2, R> _func;

        public Function(IValue<T1> param1, IValue<T2> param2, Func<T1, T2, R> pureFunction)
            : base(param1, param2)
        {
            _param1 = param1;
            _param2 = param2;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param1.Value, _param2.Value);
        }
    }

    public sealed class Function<T1, T2, T3, R> : FunctionBase<R>
    {
        IValue<T1> _param1;
        IValue<T2> _param2;
        IValue<T3> _param3;
        Func<T1, T2, T3, R> _func;

        public Function(IValue<T1> param1, IValue<T2> param2, IValue<T3> param3, Func<T1, T2, T3, R> pureFunction)
            : base(param1, param2, param3)
        {
            _param1 = param1;
            _param2 = param2;
            _param3 = param3;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param1.Value, _param2.Value, _param3.Value);
        }
    }

    public sealed class Function<T1, T2, T3, T4, R> : FunctionBase<R>
    {
        IValue<T1> _param1;
        IValue<T2> _param2;
        IValue<T3> _param3;
        IValue<T4> _param4;
        Func<T1, T2, T3, T4, R> _func;

        public Function(IValue<T1> param1, IValue<T2> param2, IValue<T3> param3, IValue<T4> param4, Func<T1, T2, T3, T4, R> pureFunction)
            : base(param1, param2, param3, param4)
        {
            _param1 = param1;
            _param2 = param2;
            _param3 = param3;
            _param4 = param4;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param1.Value, _param2.Value, _param3.Value, _param4.Value);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
New Zealand New Zealand
I am a senior software developer with almost 20 years of experience. I have extensive knowledge of C#, C++ and Assembly languages, working mainly on Windows and embedded systems. Outside of work I am interested in a wider variety of technologies, including learning 20 programming languages, developing Linux kernel drivers or advocating Functional Programming recently.

Comments and Discussions