Thursday, September 25, 2008

A Python Programmmer's Perspective on C#

Being a language fanatic, I was really excited when I met a really smart guy named Corey Kosak who gave me a tour of C#'s newest features. I had heard a lot of good things about C# lately, including that it had been strongly influenced by Haskell, which makes sense since Microsoft actually funds research on Haskell. Anyway, a lot of C#'s newest features are a lot more like Python than Java. Let me show you some examples.
Here is a sample C# iterator:
foreach(var x in CountForeverFrom(123).Take(5)) {
Console.WriteLine(x);
}
In Python, I'd write:
for i in itertools.islice(itertools.count(123), 5):
print i

C# also iterators that are similar to Python's generators. Here is the C#:
public static IEnumerable<int> CountForeverFrom(int start) {
while(true) {
yield return start;
start++;
}
}
In Python, I'd write:
def count_forever_from(start):
while True:
yield start
start += 1

C#'s LINQ syntax is similar to Python's generator expressions. Here's the C#:
var names=new[] { "bill", "bob", "tim", "tom", "corey",
"carl", "jj", "sophie" };
foreach(var x in (from name in names where name.Length>5 select name)) {
Console.WriteLine(x);
}
In Python, I'd write:
names = ["bill", "bob", "tim", "tom", "corey", "carl", "jj", "sophie"]
for x in (name for name in names if len(name) > 5):
print x

Here's a pretty amazing example that ties a lot of things together. It shows LINQ, a "group by" clause, an anonymous but strongly-typed class ("new {...}"), and even some type inferencing ("var item" and "item.FirstChar")
var crap=from n in names
group n by n[0]
into g
select new { FirstChar=g.Key,
Data=(from x in g select x).ToArray() };

foreach(var item in crap) {
Console.WriteLine(
"First group is {0} which has length {1}. The contents are:",
item.FirstChar, item.Data.Length);
foreach(var x in item.Data) {
Console.WriteLine(x);
}
}
Corey said that C#'s type inferencing is still pretty basic. It can figure out the type of a local variable, but it's definitely not as sophisticated as ML's type system. Also note that the anonymous class is more impressive that an inner class in Java because it didn't require you to use a name or an interface.

"Loosely translated", in Python I'd write:
crap = itertools.groupby(names, lambda n: n[0])
for first_char, subiter in crap:
group = list(subiter)
print "Group is %s which has length %s. The contents are:\n%s" % (
first_char, len(group), "\n".join(group))

C#'s Select method can be used like map in Python. Notice the use of an anonymous function!
var newInts=ints.Select(x => x*x);
In Python, I'd write:
new_ints = map(lambda x: x * x, ints)
The C# version runs lazily (i.e. "on the fly"), which means it only computes as much as requested. Python's map function isn't lazy. However, itertools.imap is.
The above example can also be written in LINQ style:
var newInts2=(from temp in ints select temp*temp);
In Python I'd write:
new_ints2 = (temp * temp for temp in ints)
Both the C# and the Python are lazy in this case.
If you don't want newInts to be lazy, you can do:
var intArray=newInts.ToArray();
or
var intList=new List<int>(newInts);
In Python, I'd write:
list(new_ints)

Since C# has anonymous functions, it should come as no surprise that it also has nested scopes and first-class functions (i.e. you can return a function). Although you can't nest named functions, it's easy enough to fake with anonymous functions:
private static Action<int> NestedFunctions() {
int x=5;

Action<int> addToX=newValue => {
x+=newValue;
};

addToX(34);
addToX(57);
Console.WriteLine(x);

return addToX;
}
In Python, I'd write:
def nested_functions():

def add_to_x(new_value):
add_to_x.x += new_value

add_to_x.x = 5
add_to_x(34)
add_to_x(57)
print add_to_x.x
return add_to_x

C# also has closures:
private static void BetterExampleOfClosures() {
var a=MakeAction(5);
a();
a();
a();
}

private static Action MakeAction(int x) {
return () => Console.WriteLine(x++);
}
Python has closures too. (There's a small caveat here. You can modify a variable that's in an outer scope, but there's no syntax for rebinding that variable. Python 3000 fixes this with the introduction of a nonlocal keyword. In the meantime, it's trivial to work around this problem.):
def better_example_of_closures():
a = make_action(5)
a()
a()
a()


def make_action(x):

def action():
print action.x
action.x += 1

action.x = x
return action

C#'s generics are a bit more powerful than Java's generics since they don't suffer from erasure. I can't say I'm an expert on the subject. Nonetheless, I'm pretty sure you can't easily translate this example into Java. It creates a new instance of the same class as the instance that was passed as a parameter:
public abstract class Animal {
public abstract void Eat();
}

public class Cow : Animal {
public override void Eat() {
}
}

public class Horse : Animal {
public override void Eat() {
}
}

public static T Func<T>(T a, List<T> list) where T : Animal, new() {
return new T();
}
Corey told me that while C#'s generics are stronger than Java's generics, they still weren't as strong as C++'s generics since C++ generics act in an almost macro-like way.

Python has duck typing, so it doesn't have or need generics. Here's what I would write in Python:
class Animal():
def eat(self):
raise NotImplementedError

class Cow():
def eat(self):
pass

class Horse():
def eat(self):
pass

def func(a, list_of_a):
return a.__class__()

Unfortunately, those are all the examples I have, but let me mention a few other things he showed me.

C# has a method called Aggregate that is the same as what other languages called inject or reduce.

C# has Lisp-like macros! You can pass an AST (abstract syntax tree) around, play with it, and then compile it at runtime.

C# has an interesting feature called "extension methods". They're somewhat like a mixin or reopening a class in Ruby. Using an extension method, you can set things up so that you can write "5.Minutes()". Unlike a mixin or reopening a class, they're pure syntax and do not actually affect the class. Hence, the above translates to something like "SomeClass.Minutes(5)". Although "5" looks like the object being acted upon, it's really just a parameter to a static method.

Another thing that impressed me was just how hard Visual Studio works to keep your whitespace neat. It doesn't just indent your code. It also adds whitespace within your expressions.

Ok, that's it. As usual, I hope you've enjoyed a look at another language. I'd like to thank Corey Kosak for sending me the C# code. If I've gotten anything wrong, please do not be offended, just post a correction in the comments.

13 comments:

Adam V. said...

Corey Kosak of "Forum 2000" fame?

Shannon -jj Behrens said...

Sorry, I don't know.

Anonymous said...

In your second example you state "C# also has enumerables, which Python calls generators".

Technically speaking, C# calls them iterators: http://msdn.microsoft.com/en-us/library/dscyy5s0.aspx

Shannon -jj Behrens said...

Thanks for the tip. I've updated the post.

Ants Aasma said...

I had the opportunity to use C# just recently. Language feature wise it's really quite good, all-though the type inference could be better and the option of dynamic typing wouldn't be bad. What tripped me up was the standard library implementation of datastructures. There is no easy way to compare Dictionary and List objects for equality, no type system knowledge of immutable objects, no built in FrozenDictionary. I was trying to implement a frozen multiset that could be used as a dictionary key and it was rather painful compared to how I would do it in Python - just create a small wrapper for frozendict and be done with it.

Fernando correia said...

Very interesting! Congratulations on a great article.

Shannon -jj Behrens said...

Thanks, Fernando.

Anonymous said...

You should take a look at F# next. It can run OCaml code and does some wonderful things.

Shannon -jj Behrens said...

Interesting. I wonder why they picked Ocaml.

Corey Kosak said...

> Corey Kosak of "Forum 2000" fame?

Yes, actually.

JJ and I were too busy playing Katamari for me to mention that... :-)

Shannon -jj Behrens said...

Crazy!

Anonymous said...

for i in itertools.islice(itertools.count(123), 5):

It's crazy!!! You need to write:

for i in xrange(123, 128):

Python is much better!

Shannon -jj Behrens said...

> for i in itertools.islice(itertools.count(123), 5):

Naturally, I'm a huge fan of Python too.

I'm sorry, I wasn't being very clear. I was just trying to show that you can slice and dice iterators. In real code, you should definitely use xrange as you showed. You use islice when you have some other iterator that you need to take a slice of.