Michael_Giagnocavo

In talking with several programmers via IM, there seems to be a strong resistance to taking a more functional approach to doing common tasks. I'd like to get some comments, opinions, corrections, and insults on this.

Here's an example from some production code:

Code Snippet

List<string> clauses = getClauses();

var command = clauses[0];

for (int i = 1; i < clauses.Count; i++)

{

command += " AND " + clauses[i];

}

// VERSUS

var command = getClauses()

.Aggregate((acc, s) => acc += " AND " + s);

They accomplish the same thing, except the first way, you have to anaylize the code to understand the meaning (even though it's trivial). The second way, you just have to read it to understand what it is trying to do (arguably, "Fold" or "Reduce" might be nicer than "Aggregate" but I digress). Additionally, the first way is error-prone. It'd be easy to initialize i to 0 and not notice the mistake.

Here's another example -- splitting up name=value strings.

Code Snippet

var settings = new Dictionary<string, string>();

foreach (string s in getPairs())

{

var a = s.Split('=');

settings.Add(a[0], a[1]);

}

// VERSUS

var settings = getPairs()

.Select(s => s.Split('='))

.ToDictionary(a => a[0], a => a[1]);

In practise, I've found using this approach generates more succinct code. Code should look as much like the problem it's solving. If there's repeated pattern of code, it is a weakness in the language/platform. Common patterns should be easily represented, not repeated.

But I've gotten a lot of negative feedback, saying it's ambiguous, not readable, complicated and not explicit.

What do _you_ think How can it be improved



Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

marten_range

Personally I hold the functional approach for superior when compared to the standard iterative approach.

My reasons for this:

1. Readability - when you absorbed how to think the functional approach is easier to understand

2. Composability - When writing a for loop you create a mini black box that doens't compose easily. LINQ algorithms (and your own) are composable and that makes them powerful. In your example above it would be easy to add a Where() before ToDictionary to filter out alternatives you don't like. The functional approach is a bit like the unix approach to the command line. Unix commands are intended to be small and composable and they can be very powerful when combined just because of that.

3. Productivity - I find that when I write LINQ queries I'm more productive then when writing for loops

4. Correctness - I find that when I'm finished writing my LINQ queries they tend to just work.

5. One way to your data - Regardless if you are using databases, IEnumerable<>, IQueryable<> etc you have the same syntax to query data.

6. Concurrency - Although not in .NET 3.5 today but possible to do: Since the structure of the query can be kept it is possible to write "automatic" threading of a LINQ query, ie you write your LINQ query as usual and the framework detects that you have 4 CPUs and split the work on these CPUs as well as it can manage. Compiler vendors have been struggling for years with automatic threading of for loops in C and C++ and last time I checked it didn't work that well when it got complex.

The simple for loop might still offer better performance in a single threaded environment if the work on each item in the list is small. So it still has it uses but for me the standard way of transforming a list in the future will be using the more functional approach offered by C# 3.0.

My programmer friends are all excited about LINQ and the functional approach.

Regards,

Marten

PS

The functional approach seems to be gaining ground in more places then .NET, I believe I read that Ruby will incorporate a more functional approach in the future (or was it Python )






Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

Michael_Giagnocavo

So, where do you think the main opposition is Lack of understanding what FP is



Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

orangy

Hmm, it only rarely look like this in production code.

In the second example, real code may have the following:

1. Creating dictionary with specific capacity (getPairs().Count)

2. Or creating HybridDictionary, or some other implementation of the map

3. Verifying strings as they are parsed - what if s.Split() returns array of 3 or 33 elements, or even 1 element

and so on, depending on the actual task. How would we write this in functional style

#1 and #2 can be solved by doing as follows:

Code Snippet

var pairs = getPairs();

var settings = new MyCustomDictionary<string,string>(pairs.Count) // if "pairs" is not countable, omit the capacity

settings.PopulateDictionary(pairs.Select(..).ToKeyValue(...));

As for #3, I really don't know how to do validation. However, I'm not the functional guy myself. Can you suggest how to do this






Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

Michael_Giagnocavo

For number three, you could just add in:

.Where(a => a.Length == 2)

-Michael





Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

orangy

No, this doesn't work. I don't want to filter, I want to ensure. I mean, if I'm reading some sort of INI file, I want to issue error or warning about incorrect input format or something. Filtering makes it ignore invalid occurences silently, which is wrong.




Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

marten_range

Well I'm not really an expert on parsers but one way of doing a parse of a simple ini file such as this:

Code Snippet

X = y

Pelle = 3

Tjosan = "nisse"

dsd

Test = Yxa

Fel

In C# 3.0 it could look like this:

Code Snippet

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;

namespace Parse
{
class Program
{

// Simple function that enumerates over all strings in a stream reader
static IEnumerable<string> ReadFile(StreamReader r)
{
string l_r;
while ((l_r = r.ReadLine()) != null)
{
yield return l_r;
}
}

// Regexp to validate a row in our ini-file

static Regex g_match = new Regex(
@"^\s*( <key>\S*)\s*=\s*( <value>.*)$",
RegexOptions.Compiled |
RegexOptions.Singleline);

enum ParseResult
{
Success,
Failure
}

static void Main(string[] args)
{
using (StreamReader l_r = new StreamReader(args[0]))
{
// The LINQ syntax would be prefered but I couldn't figure out how to

// get select to pass the "line number"

// Iterates over all lines in stream, determines if they are

// parseable or not
// .ToLookup splits the result into a success heap and a failure heap
var q2 =
ReadFile(l_r).Select(
(s, line) =>
{
var l_m = g_match.Match(s);
var Success =

l_m.Success ParseResult.Success : ParseResult.Failure;
var Key =

l_m.Success l_m.Groups["key"].Value : string.Empty;
var Value =

l_m.Success l_m.Groups["value"].Value : string.Empty;
return new
{
Success,
LineNumber = line + 1,
Key,
Value,
OriginalLine = s
};
}).ToLookup(key => key.Success);

// Prepares a failure report
var failure_report =
from v in q2[ParseResult.Failure]
select string.Format(

"Line {0} was invalid: {1}", v.LineNumber, v.OriginalLine);

// Writes it
foreach (var l_failed_row in failure_report)
{
Console.WriteLine(l_failed_row);
}

// Writes all succesful rows
foreach (var l_row in q2[ParseResult.Success])
{
Console.WriteLine(l_row);
}

}

Console.ReadKey();
}
}
}






Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

orangy

Thanks for an example. Well, I only can't understand the benefit of the functional style in this case. I think it would be much easier to write this in traditional way Smile






Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

marten_range

An equivalent C# 2.0 program so that we can compare:

Code Snippet

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace TestTT
{
class Program
{
static Regex g_match = new Regex(
@"^\s*( <key>\S*)\s*=\s*( <value>.*)$",
RegexOptions.Compiled |
RegexOptions.Singleline);

enum ParseResult
{
Success,
Failure
}

struct Row
{
public ParseResult Success;
public int LineNumber;
public string Key;
public string Value;
public string OriginalLine;

public Row(
ParseResult success,
int line_number,
string key,
string value,
string original_line
)
{
Success = success;
LineNumber = line_number;
Key = key;
Value = value;
OriginalLine = original_line;
}
}

static void Main(string[] args)
{
using (StreamReader l_r = new StreamReader(args[0]))
{
Dictionary<ParseResult, List<Row>> l_d =
new Dictionary<ParseResult, List<Row>>();

l_d[ParseResult.Success] = new List<Row>();
l_d[ParseResult.Failure] = new List<Row>();

string l_s;
int l_line_number = 0;
while ((l_s = l_r.ReadLine()) != null)
{
++l_line_number;

Match l_m = g_match.Match(l_s);

if (l_m.Success)
{
l_d[ParseResult.Success].Add(
new Row(
ParseResult.Success,
l_line_number,
l_m.Groups["key"].Value,
l_m.Groups["value"].Value,
l_s));
}
else
{
l_d[ParseResult.Failure].Add(
new Row(
ParseResult.Success,
l_line_number,
string.Empty,
string.Empty,
l_s));
}
}

List<string> l_failure_report = new List<string>();
foreach (Row l_row in l_d[ParseResult.Failure])
{
l_failure_report.Add(
string.Format(
"Line {0} was invalid: {1}", l_row.LineNumber, l_row.OriginalLine));
}

foreach (string l_failed_row in l_failure_report)
{
Console.WriteLine(l_failed_row);
}

foreach (Row l_row in l_d[ParseResult.Success])
{
Console.WriteLine(
@"Success = {0}, LineNumber = {1}, Key = {2}, Value = {3}, OriginalLine = {4}",
l_row.Success,
l_row.LineNumber,
l_row.Key,
l_row.Value,
l_row.OriginalLine);
}
}

Console.ReadKey();
}
}
}

Drawbacks:

1. Lengthier

2. More difficult to change (ie if I want to add another field to I need to add it to the Row class, modify the constructor, modify the print loop on the end)

3. No composability, the loop is a black box

4. No more easy lazy evaluation of the input stream

5. Have to maintain the line number count by hand (easy to forget to increment in an execution path)

6. Harder to maintain (what does the while loop do)

I'm sure you can find other things as well.

Granted it might not look like a big win since the example itself is small but still I feel the functional approach is more productive (it was faster to write, I had to debug less, it's easier to change, it's more reusable). I believe as the complexity grows it will pay of even more.






Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

orangy

I don't understand why solution should be THAT complex If my task were to parse INI file, validate and return (or output) result, I would not create extra entities like Row, Regex or ParseResult, until I need them. I'd just go line by line, parse and verify, produce results as it goes. I'm solving specific task, not creating ultimate INI parsing solution. YAGNI, KISS, etc. I would consider creating LineReader, though, which abstracts reading line-by-line from the stream, performs line counting and provides ReadLines() method of IEnumerable<string> type for easy iteration, but this is just pure OOP.

Of course, translation from functional style to traditional procedural style will add verbosity, but... Who in the clear mind will create new List and populate it with strings for the only purpose to iterate the same list once again immediately and output stored lines to the console

So, program doing almost the same would look like this:

Code Snippet

public static class IniParser
{
private static readonly Regex keyValueRegex = new Regex(@"^\s*( <key>\S*)\s*=\s*( <value>.*)$", RegexOptions.Compiled | RegexOptions.Singleline);

public static IDictionary<string, string> ParseFile(string fileName)
{
using (StreamReader reader = new StreamReader(fileName))
{
int lineNumber = 0;
Dictionary<string, string> parsedFile = new Dictionary<string, string>();
bool hasErrors = false;

string line;
while ((line = reader.ReadLine()) != null)
{
lineNumber++;
Match parsedLine = keyValueRegex.Match(line);
if (parsedLine.Success)
{
parsedFile.Add(parsedLine.Groups["key"].Value, parsedLine.Groups["value"].Value);
}
else
{
Console.WriteLine("Line {0} was invalid: {1}", lineNumber, line);
hasErrors = true;
}
}
return hasErrors parsedFile : null;
}
}
}

1. Shorter

2. No need to have Row class

3. What are you going to compose here Wink

4. It doesn't matter, whether stream is evaluated lazily or not, because we read the whole file anyway

5. Line number is incremented at one point, immediately after succesfully reading the line. LineReader mentioned above would be even better.

6. Crystal clear and easy to maintain, refactor or generalize Smile

Note, that as soon as you are going to return errors found to the caller, you will still have to declare externally visible type, which defeats your #2.






Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

Michael_Giagnocavo

How about this:

Code Snippet

var input = new[] { "foo=bar", "inference=good" };

var pairs = input.Select(s => s.Split('='));

var dic = pairs.Where(a => a.Length != 2).Any()

null :

pairs.ToDictionary(a => a[0], a => a[1]);

-Michael





Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

Peter Ritchie

I think it's lack of comfort. Programmers who have been around a while (especially coming from a C++ background) are VERY comfortable with loop semantics like for/foreach. To replace that with different semantics is a little unnerving.

It's a shame, because the functional approach is more "object oriented" in that it abstracts the logic away from a "loop". Without being tied to a for loop the implementation is then free to parallel-ize it, use caching, etc.

But, the beauty of C# 3.0 is you can use whichever semantic you're comfortable with, starting with age-old loop semantics and ramping up to a more functional approach as time permits.




Re: Visual C# 2008 (Pre-release) Request for feedback: A more functional approach?

FalconNL2007

Chalk up another supporter for adding more functional programming features to C#. The possibilities FP offers for eliminating state-related bugs, type inference and parallelization on multicore computers cannot be ignored.

As for programmers who are only comfortable with for loops: the for loop isn't going anywhere, so they can still use them. Let the rest of us who do see the benefits of FP experience the joys of LINQ, type inference, etc. I will admit that not every type of program can use it extensively and that even within one program there may be parts that are better expressed in more traditional ways. But C# is a multiparadigm language. Use FP where it helps, use OOP where it doesn't (the lack of a decent class system is one of the things keeping me from using Haskell for anything but tiny toy programs).

Personally I'd like to end up with a hybrid between C# and Haskell. All the advantages of having the .NET library, OOP, Intellisense and the good parts of the C# syntax (for me object.a().b().c() beats c(b(a(object))), as it's far easier to keep track of the parentheses) combined with Haskell's type inferrence all over the place, referentially transparent functions and the good parts of the haskell syntax (pattern matching, list and tuple handling, currying, the Haskell "layout" method of structuring code that removes the need for braces while, unlike Python, still allowing them if you don't want to put something on a new line, etc.)

As for parsing INI files, I'd probably go with something along the lines of

Code Snippet

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.IO;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var iniLines = ReadFile(@"D:\test.ini");
var errors = iniLines.Where(line => !IsValidIniLine(line));
foreach (var error in errors) { Console.WriteLine("Invalid line: " + error); }
if (errors.Any()) return;
var dictionary = iniLines.Select(line => line.Split('=')).ToDictionary(s => s[0].Trim(), s=> s[1].Trim());
foreach (var pair in dictionary) { Console.WriteLine(String.Format("Success: {0} = {1}", pair.Key, pair.Value)); }
}

static IEnumerable<string> ReadFile(string filePath)
{
var lines = File.ReadAllLines(filePath);
foreach (var line in lines) { yield return line; }
}

static bool IsValidIniLine(string line)
{
return Regex.Match(line, @"^\s*[a-zA-Z]+\s*=\s*[a-zA-Z0-9]+\s*$", RegexOptions.Singleline).Success;
}
}
}