Sunday, November 23, 2008

C# needs Linq to be (really) functional language

C# is a functional language. Although this is true for for all versions of the language there is very significant difference in usability between C# 3.0 and older releases.

To see some differences let's compare the same code fragment using some typical functional constructs written in both: C# 2.0 and 3.0. First let's write simple function, which inspects (converts to readable string) array content using C# 2.0.


public class Arrays
{
public static string Inspect<T>(T obj)
{
return obj != null ? obj.ToString() : "null";
}
public static string Inspect<T>(T[] array)
{
Converter<T, string> map = new Converter<T, string>(Inspect);
string[] converted = Array.ConvertAll<T, string>(array, map);
return "[" + String.Join(", ", converted) + "]";
}
}
Above example illustrates that functional programming with C# may be very hard to read, verbose and... so ugly!

Let see the difference when using C# 3.0, which introduces a lot of new features, inlcuding:

  • Lambda Expressions (you can easily define functions "in-line"),
  • Extension Methods (you can use bunch of helper methods added to existing types or you can add your own methods to any type, including System types),
  • Type Inference For Generics (in most cases you don't have to write types in <> brackets when invoking generic method),
  • Implicitly Typed Local Variables (you don't have to declare types for local variables),
  • Query Expressions (the most valuable and unique? feature introduced with LINQ, you can write select-from-where queries directly in C# language for different data sources inlcuding relational databases and XML files).

The same function now written in C# 3.0 is... different ;-)

using System.Linq;

public class Arrays
{
public static string Inspect<T>(T[] array)
{
var converted = array.Select(obj => obj != null ? obj.ToString() : "null").ToArray();
return "[" + String.Join(", ", converted) + "]";
}
}
Let's see in details two key code lines...

using System.Linq;
Above line is crucial for the example, because without using Linq we wouldn't be able to use bunch of useful extensions methods provided by Linq for IEnumerable interface (in our case method Select wouldn't be visible to the compiler). Here is one "trick" I wasted some time to find out and make this working. When, after adding "using System.Linq", you see following error:

The type or namespace name 'Linq' does not exist in the
namespace 'System' (are you missing an assembly reference?)

then this is most probably caused by missing reference to assembly containing Linq. This is quite obvious(?), but the name of asssembly containing Linq is not. It is System.Core.

The second line although not very pretty looks quite innocent, doesn't it?

var converted = array.Select(obj => obj != null ? obj.ToString() : "null").ToArray();
Let's see key (new) elements related to C# 3.0:
  1. We don't have to declare type for local variables, so in our example we can use

    var converted =
    instead of

    string[] converted =
  2. We can use extension methods for IEnumerable interface provided by Linq directly on IEnumerable instance. In our case we can use

    array.Select
    instead of

    Array.ConvertAll
  3. We can use type inference for invoking generic methods, so we don't have to write types in <> brackets. We can simply type:

    Select( ToArray(
    instead of

    Select<T, string>( ToArray<string>(
  4. We can use lambda expressions instead of declaring separate methods or delegates.

    obj => obj != null ? obj.ToString() : "null"
    instead of

    public static string Inspect<T>(T obj)
    {
    return obj != null ? obj.ToString() : "null";
    }
Unfortunatelly there are things we cannot do, although we would expect to be able to do them. One of the examples is, that we cannot assign lambda expression to "untyped" variable. In our example we cannot write:

var converter = obj => obj != null ? obj.ToString() : "null";
var converted = array.Select(converter).ToArray();
to compile, we would have to change this into

Func<T, string> converter = obj => obj != null ? obj.ToString() : "null";
var converted = array.Select(converter).ToArray();
that's why it's better to put this lambda expression inline, even for the cost of long and harder to read line.

As we can see C# 3.0 gives us (especially when using Linq) powerfull language features, that make the language more friendly for functional programming. Although new features are not perfect, they are indeed useful.

No comments: