Home > Software design >  Functional programming in C#, how can I handle statements like logging, rendering ui, etc?
Functional programming in C#, how can I handle statements like logging, rendering ui, etc?

Time:08-06

I read this article : https://docs.microsoft.com/en-us/archive/blogs/ericlippert/foreach-vs-foreach

and the article says:

The first reason is that doing so violates the functional programming principles that all the other sequence operators are based upon. Clearly the sole purpose of a call to this method is to cause side effects. The purpose of an expression is to compute a value, not to cause a side effect. The purpose of a statement is to cause a side effect. The call site of this thing would look an awful lot like an expression (though, admittedly, since the method is void-returning, the expression could only be used in a “statement expression” context.) It does not sit well with me to make the one and only sequence operator that is only useful for its side effects.

but I think I should executes statements (void-returning methods) like logging, rendering user UI, write into physical disk, etc... cannot this job perform with 'functional' way?

var list = new List<SomeClass>();
var query = list.Where(...).. some 'functional'-feeling combined function chains;
foreach (var log in query)
  Console.WriteLine(log);  // this is the 'right' way?
query.MyForEach(log => Console.WriteLine(log)); // this works, but violates functional programming principles

To summarize, How can I handle jobs that causes side effects by functional programming principle?

CodePudding user response:

As Simon Peyton Jones once quipped, if all you have is pure functions, the only observable effect of running them would be a hotter CPU, after which a student responded that a hotter CPU is also an observable side effect.

It's clear to everyone doing functional programming that some impure actions have to happen. You have to be able to repaint the screen, write to disk, send bits over the network, take user input, etc.

Functional programming is not about the elimination of impure actions, but it's an explicit goal to minimise them and control where they take place. A common architecture is functional core, imperative shell, which is typically like a sandwich: Gather data from impure sources, pass all the data to pure functions, and do something impure with the result.

In the code sketch given in the OP, you might split it up like this: You'd have a pure function that contains code like this:

var list = new List<SomeClass>();
var query = list.Where(/*...*/) //.. some 'functional'-feeling combined function chains;

Another part of the program - typically the entry point - would call the pure function and decide to do something with it:

foreach (var log in query)
  Console.WriteLine(log);

In C# you might as well use foreach as Eric Lippert suggests, since that's the most idiomatic.

You could also use a ForEach extension method. Some people do that, but it makes no difference. Such an action is inherently impure, so it doesn't make it any more or less functional.

I'm not sure I completely agree with Eric Lippert's position, but I acknowledge the argument. Even though, a nice ForEach extension method can sometimes save a line of code.

It's not as though actions like that doesn't exist in Haskell, which has mapM_ and forM_ for exactly such purposes.


Historically, there's been a few failed attempts at figuring out how to best model impure actions in functional programming - particularly in statically typed languages - but for the last few decades, the general solution is to model effects with monads.

Often you can model things in simpler ways by thinking about things differently, but if all else fails, you can always model local state mutation with the State monad.

If you also need to perform I/O (and you always do), Haskell offers the opaque IO monad. You wouldn't use something like IO in C#, but it's interesting to think about what that would look like.

  • Related