Side Effects and Functional Programming

One of my first posts at CodeBetter was in regards to side effects and how, when unmanaged, can be truly evil.  Today, I want to revisit that topic briefly in regards to functional programming and managing side effects.  When I was out in Redmond a couple of months ago, I had the opportunity to sit down with Erik Meijer to discuss functional programming among other topics.  In there, we discussed a number of issues around managing side effects and state in your code, and how both C# and F# don't intrinsically support such a concept.  Languages like Haskell, of course do with IO monads and other such monadic structures.  Whether languages such as F# and Erlang are not pure functional programming languages is another matter, due to the fact that you don't have to declare when you are side effecting (reading a database, writing to console, spawning a process, etc). 

Erik will be giving a talk at JAOO about why functional programming still matters.  I think in the day and age of where Moore's law is changing, we need to recognize that functional programming has a unique place in this due to the way that it handles both purity and immutability.  Today, let's cover some of those issues when dealing with functional constructs and side effects to see what kind of an impact they can have on us.


The Problem of Purity

In some previous posts, I talked about function purity in regards to things you should keep in mind with a functional mindset.  In order to qualify as such, you need to meet the following criteria:

  • Evaluate with the same result given the same input, and perform no state change.
  • Evaluation of the given function does not cause observable side effects (write to console, write to database, etc)

Things get even more clouded as we bring lazy evaluation to the table.  One of the great things that came to C# in 2.0 and especially in 3.0 with LINQ was the idea of lazy evaluation through the yield keyword.  This gave us the ability to defer work until it was absolutely needed.  That's one of the hallmarks of functional programming, especially a lazy language such as Haskell.

Let's look at a code snippet both written in F# and the equivalent in C# and let's determine the order of side effects.  Can they be predicted here?  When do the side effects happen?  Do I just get a nice list of my integers at the end?

F#
#light

let divisible_by_two n = 
  printf "%i divisible by two?" n
  n % 2 = 0

let divisible_by_three n =
  printf "%i divisible by three?" n
  n % 3 = 0
  
let c1 = {1 .. 100} |> Seq.filter divisible_by_two
let c2 = c1 |> Seq.filter divisible_by_three

c2 |> Seq.iter(fun c -> printfn "%i" c)

C#
static bool DivisibleByTwo(int n)
{
    Console.Write("{0} divisible by 2?", n);
    return n % 2 == 0;
}

static bool DivisibleByThree(int n)
{
    Console.Write("{0} divisible by 3?", n);
    return n % 3 == 0;
}

var c1 = from n in Enumerable.Range(1, 100)
         where DivisibleByTwo(n)
         select n;

var c2 = from n in c1
         where DivisibleByThree(n)
         select n;

foreach (var c in c2) Console.WriteLine("{0}", c);
 

Well, what do you think?  If you said that the list will print out nicely at the end, you'd be very wrong.  In fact, our output might look something like this:

console_impure_divisible

Why is that?  Well, because in LINQ, the return type is always IEnumerable<T>, and in the case of F#, I was using sequences, which unlike the List<'a>, is lazily evaluated.  What can we do about it?  That's another matter altogether.  Unfortunately, the language constructs in .NET don't forbid such things, so there's no way of really preventing it, instead, you must be vigilant on your own to make sure this doesn't happen.  Spec# can help in just a little way as it can ensure state change will not occur during a given operation, but does not prevent you, nor warn you if you do such things as write to the console, logs, databases, etc.  Let's move onto another example and let's see what happens.


Exception Management and Lazy Evaluation

Exception management in terms of lazy evaluation is another interesting topic.  When it comes to lazily evaluated functions and structures, how do we handle exceptions that may occur is a very good question.  Let's look at an example of trying to catch an exception that may happen in our code due to a DivideByZeroException.  What do you think will happen here?  Will it be caught and just return us an empty collection?  Let's find out:

F#
#light

let numbers = seq[1 ; 5; 2; 3; 7; 9; 0]

let one_over n =
  try
    n |> Seq.map(fun i -> 1 / i)
  with
    | err -> seq[]
    
numbers |> one_over |> Seq.iter(fun x -> printfn "%i" x)
C#
static IEnumerable<int> OneOver(IEnumerable<int> items)
{
    try
    {
        return from i in items select 1/i;
    }
    catch
    {
        return Enumerable.Empty<int>();
    }
}

var numbers = new[] {1, 5, 2, 3, 7, 9, 0};
foreach (var number in OneOver(numbers)) 
    Console.WriteLine("{0}", number);
 

What did you expect?  Did we in fact catch the exception?  The answer is no, because the returned structure isn't evaluated until later, there isn't any way for this try/catch block around our code to possibly work.  The question is to you, how might you fix this?

The logic here is fundamentally flawed.  Let's move onto another scenario, this time dealing with resource management.


Resource Management and Lazy Evaluation

Another area of exploration is around resource management and lazy evaluation.  How do you ensure that your resources will be cleaned up once your evaluation is complete?  It's relatively easy to make minor mistakes that may come back and haunt us.  Let's look at a quick sample of using lazy evaluation and reading a file to the completion.  What happens in the following code?

F#
#light

open System.IO

let readLines = 
  use reader = File.OpenText(@"D:\Foo.txt")
  let lines() = reader.ReadToEnd()
  lines

printfn "%s" (readLines())
C#
Func<string> readLines = null;

using(var stream = File.OpenText(@"D:\foo.txt"))
    readLines = stream.ReadToEnd;

Console.WriteLine(readLines());
 

The answer of course is that we get an ObjectDisposedException thrown due to the fact that the stream is long gone by the time we want to invoke our function of readLines.  Our reader has long fallen out of scope, and therefore we get that exception.  So, how do we fix it?


Closures and Lazy Evaluation

One last topic for this post revolves around variable instantiation around closures and what it means to you in regards to lazy evaluation.  Let's look at a quick example of some of the issues you might face. 

C#
var contents = new List<Func<int>>();
var s = new StringBuilder();

for (var i = 4; i < 7; i++)
    contents.Add(() => i);

for (var k = 0; k < contents.Count; k++)
    s.Append(contents[k]());

Console.WriteLine(s);
 

What we might expect the results to be in this case would be 456.  But that's not the case at all here.  In fact, the answer you will get is 777.  Why is that?  Well, it has to do with the way that the C# compiler creates a helper class to enable this closure.  If you're like me and have Resharper, you'll notice that it gives a warning about this with "Access to modified closure".  If we change this to give ourselves a local variable inside the loop closure construct to initialize the value properly.  If we do that and change our code, it will now look like this:

C#
var contents = new List<Func<int>>();
var s = new StringBuilder();

for (var i = 4; i < 7; i++)
{
    var j = i;
    contents.Add(() => j);
}

for (var k = 0; k < contents.Count; k++)
    s.Append(contents[k]());

Console.WriteLine(s);
 

Jason Olson has a pretty good explanation in his post "Lambdas - Know Your Closures".  But where does F# fall into this picture?  Well, let's try the first code sample written in F# to see what kind of results that we get.

F#
#light

open System.Text

let contents = new ResizeArray<(unit -> int)>()
for i = 4 to 6 do
  contents.Add((fun () -> i))
 
let s = new StringBuilder()
for k = 0 to (contents.Count - 1) do
  s.Append(contents.[k]()) |> ignore
 
printfn "%s" (s.ToString())

When we run this code, we get what we were expecting all along, "456".  Why is that?  The F# team handles the code just a little bit differently than the C# compiler does.  This to me seems a lot less error-prone.


Wrapping It Up

I'd like to see how you might fix some of these issues?  What would you do differently in each example to ensure the correct result with lazy evaluation?

Functional programming and side effects are important topics.  I hope this post shed some light on how with lazy evaluation, side effects, when not managed properly, can be quite evil.  The ideas in functional programming is to strive towards a side effect free style.  When we start introducing lazy evaluation, concurrency and other issues, we need to be mindful of side effects and how we manage them. 

I highly encourage you to see Erik's presentation that I linked above as it contains ideas that may help make you a better programmer.  And while you're at it, watch the video of Simon Peyton Jones and Erik Meijer on Channel 9 called "Towards a Programming Nirvana".  It's definitely an eye opener!



kick it on DotNetKicks.com

2 Comments

  • C# Version of Resource Management:

    Func ReadLines = () =>
    {
    using (var fs = File.OpenText("abc.txt"))
    {
    return fs.ReadToEnd();
    }
    };

    Console.WriteLine(ReadLines());

  • @Logicalmind

    Yes, to move the scope of the reader within the function itself would definitely be an answer to the problem. To contain the IO action is probably the best bet, or avoid laziness altogether.

    Matt

Comments have been disabled for this content.