One of the things to keep in mind when learning a new programming language is that it isn’t enough to learn the syntax and semantics of various language features you are unfamiliar with. Just as important is learning the idioms and way of thinking that goes with these language features. On reddit, ubernostrum pointed out that my usage of
if all_links.get(url) == None:
was jarring to read as a Python programmer when compared to the more idiomatic
if url not in all_links:
Of course this is just a stylistic issue but his point is valid. A similar thing happened with regards to other aspects of my recent post entitled Does C# 3.0 Beat Dynamic Languages at their Own Game?
I argued that type inferencing and anonymous types in C# 3.0 did not offer the same degree of functionality that tuples and dynamic typing did when it came to processing intermediate values in a computation without requiring nominal types (i.e. named classes) to hold these values.
Specifically I had the following IronPython code,
IronPython Code for item in filteredItems: vote = (voteFunc(item), item, feedTitle) #add a vote for each of the URLs for url in item.outgoing_links.Keys: if all_links.get(url) is None: all_links[url] = [] all_links.get(url).append(vote) # tally the votes, only 1 vote counts per feed weighted_links = [] for link, votes in all_links.items(): site = {} for weight, item, feedTitle in votes: site[feedTitle] = min(site.get(feedTitle,1), weight) weighted_links.append((sum(site.values()), link)) weighted_links.sort() weighted_links.reverse()
IronPython Code
for item in filteredItems: vote = (voteFunc(item), item, feedTitle)
#add a vote for each of the URLs for url in item.outgoing_links.Keys: if all_links.get(url) is None: all_links[url] = [] all_links.get(url).append(vote)
# tally the votes, only 1 vote counts per feed weighted_links = [] for link, votes in all_links.items(): site = {} for weight, item, feedTitle in votes: site[feedTitle] = min(site.get(feedTitle,1), weight) weighted_links.append((sum(site.values()), link)) weighted_links.sort() weighted_links.reverse()
The key things to note about the above code block are (i) the variable named vote is a tuple of three values; the numeric weight given to a link received from a particular RSS item, an RSS item and the title of the feed Python and (ii) the items in the tuple can be unpacked into individual variables when looping over the contents of the tuple in a for loop.
for
When I tried to write the same code in C# 3.0 with a vote variable that was an anonymous type, I hit a road block. When I placed instances of the anonymous type in the list, I had no way of knowing what the data type of the object I’d be pulling out of the list would be when I wanted to extract it later to tally the votes. Since C# is statically typed, knowing the type’s name is a requirement for retrieving the objects from the list later unless I planned to interact with them as instances of System.Object and access their fields through reflection (or something just as weird).
So in my C# 3.0 solution I ended up creating RankedLink and Vote types to simulate the functionality I was getting from tuples in Python.
However it turns out I was using anonymous types incorrectly. I tried to take a feature that was meant to be coupled with C# 3.0’s declarative Language Integrated Query (LINQ) and use it in the traditional imperative loop constructs I’ve been familiar with since my days programming in C.
Ian Griffith’s set me straight with his blog post entitled Dare Obasanjo on C# Anonymous Types where he showed how to use anonymous types to get the solution I wanted without having to create unnecessary named types to hold intermediate values. Ian’s code is shown below
C# 3.0 Code// calculate vote for each outgoing url var all_links = from item in items from url in item.OutgoingLinks.Keys group item by url into itemUrlGroup select new { Url=itemUrlGroup.Key, Votes=from item in itemUrlGroup select new { Weight=voteFunc(item), Item=item, FeedTitle=feedTitle } }; // tally the votes var weighted_links = from link_n_votes in all_links select new { Url=link_n_votes.Url, Score=(from vote in link_n_votes.Votes group vote by vote.FeedTitle into feed select feed.Min(vote => vote.Weight) ).Sum() } into weighted_link orderby weighted_link.Score descending select weighted_link;
C# 3.0 Code
// calculate vote for each outgoing url var all_links = from item in items from url in item.OutgoingLinks.Keys group item by url into itemUrlGroup select new { Url=itemUrlGroup.Key, Votes=from item in itemUrlGroup select new { Weight=voteFunc(item), Item=item, FeedTitle=feedTitle } }; // tally the votes var weighted_links = from link_n_votes in all_links select new { Url=link_n_votes.Url, Score=(from vote in link_n_votes.Votes group vote by vote.FeedTitle into feed select feed.Min(vote => vote.Weight) ).Sum() } into weighted_link orderby weighted_link.Score descending select weighted_link;
As you can see, Ian’s code performs the same task as the Python code does but with a completely different approach. The anonymous types are performing the same function as the Python tuples did in my previous code sample and there is no need to create RankedLink and Vote types to hold these intermediate values.
What I find interesting about this is that even though I’ve been using C# for the past five or six years, I feel like I have to relearn the language from scratch to fully understand or be able to take advantage the LINQ features. Perhaps a few stints as a SQL developer may be necessary as well?