0

I like the idea of writing pure functions, but I'm having trouble understand ways to combine them that lead to testable code. I'm used to extracting classes and then stubbing appropriately, and feel that I'm missing some key insight into functional programming.

Here is an example that I've slimmed down from a problem I'm currently facing.

I want to take a list of a list of dates and filter it for those which meet an 'opportunity' criteria.

In C# it looks something like:

    static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
    {
        return dates.Where(ds => HasOpportunity(ds)).ToList();
    }

    static bool HasOpportunity(List<DateTime> dates)
    {
        var threshold = 0.05D;

        var current = OpportunityProbability(dates, DateTime.Now);
        var previous = OpportunityProbability(dates, DateTime.Now.Subtract(TimeSpan.FromDays(30)));

        return previous >= threshold && current < threshold;
    }

    static double OpportunityProbability(List<DateTime> dates, DateTime endDate)
    {
        // does lots of math
        return 0.0D;
    }

So at the tip we have OpportunityProbability that I know how to test. The trouble I have is in HasOpportunity and further up the chain (Opportunities).

The only way I know how to test HasOpportunity is by stubbing out OpportunityProbability but I can't do that. And I don't want to create fake data to satisfy the design of OpportunityProbability in order to test HasOpportunity. So even though both functions are pure, it isn't testable, and I feel like its bad design.

And thus I feel like I'm designing bad Functional Code :)

What I care about with HasOpportunity is mostly the boolean test. Given two doubles and a threshold do the comparison and return the result. In order to get those two doubles it uses a function which requires a list of dates and a date. This leads HasOpportunity to also be responsible for determining the dates (DateTime.Now and 30 days before). Maybe I can split that out:

    static bool HasOpportunity(double probability1, double probability2)
    {
        var threshold = 0.05D;

        return probability2 >= threshold && probability1 < threshold;
    }

So that is clearly testable. I could even move the threshold up:

    static bool HasOpportunity(double threshold, double probability1, double probability2)
    {
        return probability2 >= threshold && probability1 < threshold;
    }

So this is even more generic.

The issue I run into when I do this is that I've just moved things up to Opportunities:

    static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
    {
        return dates.Where(ds => {
            var current = OpportunityProbability(ds, DateTime.Now);
            var previous = OpportunityProbability(ds, DateTime.Now.Subtract(TimeSpan.FromDays(30)));
            return HasOpportunity(0.05D, current, previous);
        }).ToList();
    }

This is where I don't know the next step to take.

Any thoughts oh functional overlords? Help me write F# in C#, thanks in advance!

update

So taking it another step, I can get:

    static List<List<DateTime>> Opportunities(double threshold, DateTime currentDate, DateTime previousDate, List<List<DateTime>> dates)
    {
        return dates.Where(ds => {
            var current = OpportunityProbability(ds, currentDate);
            var previous = OpportunityProbability(ds, previousDate);
            return HasOpportunity(threshold, current, previous);
        }).ToList();
    }

So I still don't know how to test this, but it is nice in that the parameters to this function end up making a definition of what the opportunity is:

  • threshold
  • first date
  • second date

And then given a list of a list of dates it can give you the opportunities.

5
  • care to comment why a downvote? Commented Jun 16, 2013 at 17:09
  • "Any thoughts oh functional overlords?" Thoughts on what? How to test built-in operators? "Help me write F# in C#, thanks in advance!" What F#? Commented Jun 16, 2013 at 17:12
  • I feel this is pretty generic code. It is in C# because I had to pick one, but I could easily rewrite this in F#. The whole question is based on how to combine pure functions in a testable way. I could write that more abstract, but I don't think it helps anything. Commented Jun 16, 2013 at 17:17
  • I think your last step there is fine -- since it's a pure function, you could use the randomized testing features in NUnit, or perhaps FsCheck (which can also be used for C# code). Commented Jun 16, 2013 at 17:17
  • I just want to delete the question since its considered stupid. Commented Jun 18, 2013 at 15:58

2 Answers 2

2

Did you consider using higher-order functions? Pass in the OpportunityProbability functions into HasOpportunity.

static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
{
    return dates.Where(ds => HasOpportunity(ds, OpportunityProbability, OpportunityProbability)).ToList();
}

static bool HasOpportunity(List<DateTime> dates, Func<List<DateTime>, DateTime, double> currentProb, Func<List<DateTime>, DateTime, double> prevProb)
{
    var threshold = 0.05D;

    var current = currentProb(dates, DateTime.Now);
    var previous = prevProb(dates, DateTime.Now.Subtract(TimeSpan.FromDays(30)));

    return previous >= threshold && current < threshold;
}

static double OpportunityProbability(List<DateTime> dates, DateTime endDate)
{
    // does lots of math
    return 0.0D;
}

Now you can test both OpportunityProbability and HasOpportunity independent of each other (in the case of HasOpportunity you "stub" the second and last parameter. If you wanted more separation you could pass in OpportunityProbability into Opportunities as well.

Sign up to request clarification or add additional context in comments.

2 Comments

I did think about that, but didn't try it out. These things are the intuition I'm looking to build for FP. I don't actually care about testing the code, I am just using it as a proxy for design. Thanks for comment.
You might find the following article useful, generally it's a great website for F# and functional programming techniques: fsharpforfunandprofit.com/posts/recipe-part1
0

I think you should throw in a bit of good-old object orientation, and honour the Single Responsibility Pattern. One possible way is to create classes:

  • OpportunityCalculator with method double OpportunityProbability(List<DateTime> dates, DateTime endDate)

  • OpportunityFilter with method bool HasOpportunity(double threshold, double probability1, double probability2)

These classes could be tested independently:

  • OpportunityCalculator abstracts out the complex math.
  • While testing OpportunityFilter, you can stub out OpportunityCalculator. Your tests would be centred around the fact that the calculator would be consulted twice with the correct parameters.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.