F# vs C# – A simple comparison

A while back I blogged about a situation where thinking out of the box helped me write some code more quickly. In a nutshell, I needed to extract an exchange rate from an XML feed, and found that by using F# to do it, then translating the code back to C#, it was much easier than trying to write the code in C# in the first place.

I thought it might be interesting to revisit this in both languages, and compare the two experiences. F# first, as I know that’s going to be easy :). I’ll probably spend more time explaining what I did in F# as it was more interesting.

F#

As I couldn’t be bothered digging out the URL for the Google Maps API call, I did a quick search and found a site with a free XML feed for exchange rates. The URL for the GBP feed is http://www.floatrates.com/daily/gbp.xml, which I duly fed into an F# type provider…

type Exchange = XmlProvider<"http://www.floatrates.com/daily/gbp.xml">

Having sent this to FSI, it was embarrassingly easy to get the first exchange rate…

Exchange.GetSample().Items.[0].ExchangeRate

OK, but I need to search the feed and find the right currency. First, let’s take a small digression and see what currencies are in there. This was also pretty simple…

let getCurrencies =
  xr.Items
  |> Array.sortBy (fun x -> x.TargetName)
  |> Array.iter (fun x -> printfn "[%s] %s" x.TargetCurrency x.TargetName)

This may not be the best way to do this (I’m no F# expert), but it was quick and easy, and gave me what I wanted…

[KMF] 	Comoro franc
[AFN] Afghan afghani
[ALL] Albanian lek
[DZD] Algerian Dinar
[AOA] Angolan kwanza
[ARS] Argentine Peso
[AMD] Armenia Dram
[AWG] Aruban florin
[AUD] Australian Dollar
[AZN] Azerbaijan Manat
...and a whole lot more...

I’d never even heard of most of these!

Anyway, back to the main theme of today’s symposium, I wanted to extract the exchange rate for a a given currency. My first attempt at this was as follows…

let getXr curr =
  xr.Items
    |> Seq.tryFind (fun x -> x.TargetCurrency = curr)
    |> (fun x ->
          match x with
          | Some(xch) -> xch.ExchangeRate
          | None -> -1.0m
        )

This works, but isn’t so elegant. Thirty seconds’ thought led to a neater version…

let getXr2 curr =
  xr.Items
    |> Seq.tryFind (fun x -> x.TargetCurrency = curr)
    |> function | Some(xch) -> xch.ExchangeRate | None -> -1m

Hmm, better, but I’m taking an option returned from Seq.tryFind, matching on it and returning a number if the option were None. This kind of defeats the idea of having an option type in the first place. If the currency can’t be found, I should be returning None. That’s what it’s there for! So, I modified it to the following…

let getXr3 curr =
  xr.Items
    |> Seq.tryFind (fun x -> x.TargetCurrency = (string curr).ToUpper())
    |> Option.map (fun x -> x.ExchangeRate)

This was much better. All in all, this took about five minutes. I reckon that had I been more familiar with F# and functional programming in general, I would have gone straight for the final version, as it is more idiomatic to return an option when the target currency cannot be found.

Whilst I was at it, I decided to turn this into a more generic (with a small “g”) function, and allow both the source and target currencies to be passed in. This was very complex (not!!), and took me at least three more minutes (only because I had to look up the syntax for try/with in F#). My function looked like this…

let getXrate sourceCurrency targetCurrency =
  try
    let rates = Exchange.Parse(Http.RequestString(sprintf "http://www.floatrates.com/daily/%s.xml" sourceCurrency))
    rates.Items
      |> Seq.tryFind (fun x -> x.TargetCurrency = (string targetCurrency).ToUpper())
      |> Option.map (fun x -> x.ExchangeRate)
  with
    _ -> None

This can be used as following (exchange rates correct as of right now)…

> getXrate "gbp" "usd";;
val it : decimal option = Some 1.35221236M

> getXrate "usd" "gbp";;
val it : decimal option = Some 0.73952881M

> getXrate "gbp" "xyz";;
val it : decimal option = None

All works fine.

C#

OK, so I now had to try and do the same in C#. This didn’t take as long as I expected, but was a little painful, as I had to guess at what XPath selectors were needed, run the code, dump the output to the console, examine it to see where I was up to in the XML file, then rinse and repeat until I finally found the correct point.

I won’t bore you with each iteration, I’ll just bore you with my final method…

public float GetExchangeRate(string source, string target) {
  try {
    WebClient wc = new WebClient();
    string xml = wc.DownloadString("http://www.floatrates.com/daily/" + source + ".xml");
    XElement x = XElement.Parse(xml);
    var item = x.Elements("item").FirstOrDefault(e => e.Element("targetCurrency").Value == target.ToUpper());
    if (item == null) {
      return 0;
    }
    float fx = float.Parse(item.Element("exchangeRate").Value);
    return fx;
  } catch {
    return 0;
  }
}

This works as expected, although I had to return zero in case of a failure, which is not as neat as returning an option.

So, let’s compare the two approaches…

Ease of development

No competition here, F# wins hands down. The type provider gave me a strongly-typed graph, which I could examine using Intellisense. This allowed me to  work out the path to the desired element in a few seconds.

By comparison, in C# I had to drill my way down manually using the Element and Elements extension methods. As mentioned above, this was a hit-and-miss approach, which didn’t take that long, but wasn’t particularly pleasant. It also meant re-running the code (including making a network request) every time I changed anything, as opposed to not having to re-run the F#, as Intellisense worked with what was already in memory.

Readability of the final code

Again, F# wins outright here. Assuming you are comfortable with F# syntax, then the intent of the code is very clear. The main content of the function is on lines 3-6, enabling you to concentrate on just four lines to see what’s going on. Lines 2, 7 and 8 are not significant (like curly braces in C#, see the note at the end of this post), so you don’t need to exert much mental effort on them.

By contrast, the C# code is a lot harder to read. It takes three lines of code (3-5) to get the data, line 6 to find the appropriate element, three more lines to return zero if it wasn’t found (although I could have skipped those three lines and relied on the exception handling to return zero, but that’s not a very neat way to do it), and the another line (10) to extract the exchange rate itself. Your brain has to concentrate on a lot more to see what’s going on, and the structure of the data set isn’t obvious from the code.

The C# code also relies on hard-coded element names, which is always a smell.

Length of the final code

Whilst lines of code is not always a good indicator of the quality of the code, it is something to consider, as the more code you have to read, the more your brain has to work at putting it all together. On the other hand, I have seen (and written) code that packs everything into as few lines as possible, at the expense of being able to understand what’s going on.

In this example, the F# code is slightly shorter (ignoring braces, see note at the end), but given that the whole function isn’t that long, this is perhaps not significant.

Conclusion

So, for this simple example, F# came out a very clear winner. Now in fairness, it should be pointed out that this is largely due to the fact that the F# team have implemented the uber-fab type providers feature. For reasons that completely escape me, the C# team haven’t done this. As far as I can see, there’s nothing inherently F# about type providers, they could equally have been implemented in C#. Having said that, the fact is that we have them in F# and we don’t have them in C#, so when you need them, F# is clearly a better choice.

Does this prove that F# is better than C#? No, it just proves that there are use cases where it is better. This was one.

By contrast, if the example had included a user interface, I suspect the conclusion would have been very different. C# has some superb tooling for helping you create user interfaces, whether for the desktop or the web, whereas F# doesn’t have anything built in. It is possible to do UIs in F#, but it’s painful. I cringe when I see videos and articles where F# fanboys are so determined not to use even a single line of C# that they jump through hoops to build a UI in F#. I’m a big fan of using the right tool for the right job. In cases like the one above, F# was the right tool. For UIs, C# is the right tool. Anyone with any sense would build the back end in F# and the UI in C#, giving you the best of both worlds.

A note about lines of code

One of the common criticisms that the F# fanboys throw at C# is that the curly braces add extra lines of code. Sorry to break the truth to them, but C# (in common with C++, Java, JavaScript, etc) programmers don’t actually look at curly braces very often. Our brains subconsciously process them to keep track of code blocks, and the reformat feature of Visual Studio uses them to indent the code for us, but we very rarely read them.

Therefore, any comparison of lines of code between the two languages should be honest, and ignore curly braces.

2 Comments

  1. Bryan said:

    This is great! Finally a clear use case for F#.

    However, surely you wouldn’t create a whole application in F# based on this one benefit would you? I mean, type providers are excellent, but there’s more to a LOB application than just that.

    Thanks for a great article.

    May 10, 2018
    Reply
    • Avrohom Yisroel Silver said:

      Brian,

      You’re right, this one point wouldn’t necessarily justify moving to F#, but it’s an indication of how type providers can ease your coding. If you were writing an C# application that used a lot of this kind of request, then it would be worth farming it all out to an F# class library, and calling that from your C# code.

      As I said in the post, my strong belief is that you need to chose the right tools for the right job. Building a UI is definitely easier in C#, so you’d have to be pretty dumb to use F# for it. It’s just making work for yourself. If the back-end coding is better in F# (which it may be for some parts, it may not be for others), then use whichever language is better for each part.

      May 10, 2018
      Reply

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.