From C# to F#: A Developer's Perspective

Over the last few years C# has been gaining more and more functional features. C# 2.0 introduced generics, anonymous methods, and iterators. C# 3.0 added lambda expressions, implicitly typed local variables and LINQ. All of these features have their roots in functional programming.

About three years ago I started an enterprise project that would begin to change my thinking on C# and object-oriented design. I had always been a keen C# .NET developer, specializing in enterprise server-side development. During the course of the project various issues and problems led me to a more functional way of thinking about problems and the uptake of a new language F#.

A colleague and I took over from an existing team that had failed to deliver a message routing gateway that could reliably deliver messages and perform at the required volume. Various issues were found such as: threading deadlocks, data structures that weren’t thread safe, under-performing code, and unnecessary complexity. One of the interesting things that came out of analysing the problem was that the new design that emerged was very functional in nature, although I wasn’t aware of this at the time due to my lack of knowledge of functional programming.

I initially started looking at the F# language due to claims that it helped in writing concurrent and parallel programs. On further investigation the concepts in the language struck a chord in me and had many parallels with the type of code I was writing. Unfortunately I wasn’t able to introduce F# into the project but the design was heavily influenced by my changing thought processes. Since then my whole way of looking at problems has changed a lot for the better. For example when I think about the inheritance hierarchies I used to create they were deep & wide, so that a single function could differ - I no longer need to do that, instead I can instead use functional composition. I also no longer think in terms of objects with mutable state, and the data structures I use tend to be very simple and immutable.

The scope of this article is not to introduce the F# language and its syntax but to look at some of the key differences between the C# and F# languages and some of the concepts that functional languages lead you to think about.

Language Differences

There are some big differences between C# and F#; both are multi paradigm languages. A programming paradigm is a fundamental style of programming, or a way of solving a software engineering problem.

  • C# mostly uses an imperative style of programming; the problem is broken down into a series of statements that changes a programs state.
  • F# emphasises functional programming, where a problem is solved with mathematical functions or transformations that avoid state and mutable data.

Here are five such differences you’ll need to wrap your head around.

Pure Functions

A function that has no side effects or mutable data is known as a pure function. A pure function will always evaluate to the same result given the same input arguments. In addition, the evaluation of the result will not cause any observable side effects or output. A function with side effects would, in addition to producing an output, modify some state or interact with the calling function. Pure functions have a number of interesting properties and benefits:

  • If the result of a pure function is not used then the function itself can be removed without affecting other areas of the code.
  • The result of a pure function can be cached, as the result will be constant with respect to the input, this is known as referential transparency. You could effectively replace the function with a value.
  • If there is no dependency between pure functions, they can be run in any order and in parallel without interfering with each other; in other words the result of a pure function is thread-safe, and can be parallelised.

The last point is of enormous benefit, a lot of the complexity and difficulty in multithreaded programming lies in the synchronisation threading constructs such as locks, semaphores and wait handles that have to be used to protect access to mutable data structures and objects. If you remove the mutability then essentially these problems go away.

When several threads are blocked waiting for access to an object then this can lead to an increase in context switching. A context switch is the process of storing and restoring the state of a thread so that execution can be resumed at a later point in time. This can use a significant portion of the CPU when switching rapidly between threads.

Immutable Objects

An immutable object is an object whose state cannot be modified after it is created. In C# you can achieve this by making all the fields read-only and having only get accessors on any properties. All methods on the object would not modify the internal state; if a change to the object was required then a new instance of the object would have to be returned by the method.

In C#, variables are mutable by default. In F#, everything is immutable by default and you have to explicitly use the mutable keyword and the destructive assignment operator <- to specify that you want to allow the reassignment of a value.

Efficient use of space

In C#, a lot of space is devoted to ending each statement with a semicolon and enclosing sections of code in curly braces. By default F# runs in light mode which means you do not have to specify curly braces and semicolons to separate areas of code. This means that in F# lines end with a carriage return rather than a semicolon and you indicate the body of a function or a change of scope by indenting the line.

The following code shows a function that takes a string as an input, and then splits the string into sub-strings based on each occurrence of the ‘.’ (period) character. Each sub-string is then converted into bytes and returned in an array.

Lines 1 and 2 show the equivalent F# for a C# using declaration.

open System
open System.Text
let convertSentenceToBytes (s:String) = 
  s.Split [|'.'|] |> Array.map Encoding.UTF8.GetBytes
  • Line 3 shows a function declaration, we are using a type annotation at the end of the line (s:String) because the Split function (s.Split) on line 4 has several overrides and the compiler doesn’t know what one is applicable.
  • Line 4 shows the pipeline operator |>. This operator takes a value of one type on the left hand side and a function on the right.

The next code listing achieves the same thing, but you have to read from right to left to see what the functions are doing. By using the pipeline operator you can produce code that is concise and that is also more visually appealing.

open System
open System.Text
let convertSentenceToBytes (s:String) =
  Array.map (fun (s:String) -> Encoding.UTF8.GetBytes(s)) (s.Spilt [|'.'|])

Type Inference

One of the first things I noticed in F# is the lack of type definitions. F# is a statically typed language so every type is deduced during compilation. The compiler uses the context surrounding parameters to infer the type information. If there is no single inferred type then the compiler reports an error. When the compiler cannot deduce the type information then type annotations can be used to specify the type information. You can use type annotations on parameters, expressions, and return values. These are all shown in the following code.

// Type annotations on a parameter
let add (x : int) y =
    x + y

// Type annotations on an expression
let add x y =
    // Notice that theere is no type annotation present above
    // The compiler infers y's type from the + operator and x's type
    (x : int) + y

// Type annotation on a return value
let add x y : int =
    x + y
  • Line 2 shows type a type annotation applied to an input parameter to a function (x : int)
  • Line 9 shows a type annotation been applied to an expression within a function (x : int). Notice that an annotation is not required anywhere else in the function.
  • Line 12 shows a type annotation applied on the return type of a function

C# 3.0 added implicitly typed local variables with the var keyword but the var keyword cannot be applied to fields or method parameters.(See the remarks section here)

Asynchronous Programming

Asynchronous workflows are for performing computations concurrently without blocking the current thread. This is a common scenario when dealing with network or file IO operations that are expected to take a large amount of time. Or a graphical user interface application that need to perform a complex background tasks without the user interface becoming sluggish. The recent and continued trend in CPU hardware is to put more and more cores into processors and embracing concurrent programming is a distinct advantage.

Listing 4 is an example of an asynchronous workflow from the msdn site. This sample downloads urls from a list in parallel and asynchronously.

open System.NET
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
                "MSDN", "http://msdn.microsoft.com/"
                "Bing", "http://www.bing.com"
              ]

let fetchAsync(name, url:string) =
    async { 
        try
           let uri = new System.Uri(url)
           let webClient = new WebClient()
           let! html = webClient.AsyncDownloadString(uri)
           printfn "Read %d characters for %s" html.Length name
        with
           | ex -> printfn "%s" (ex.Message);
    }

let runAll() =
    urlList
    |> Seq.map fetchAsync
    |> Async.Parallel 
    |> Async.RunSynchronously
    |> ignore

runAll()

There are three main sections in this example:

  • Lines 4-7 show a list or urls being created. The syntax for a list is square brackets with a comma between each item in the list.
  • Line 9 shows a function called fetchAsync. This defines an asynchronous workflow and is declared by the expression async {…}. Inside this block you can define standard synchronous operations using the normal let expressions and asynchronous operation with the let! keyword.
    1. On line 12 a new Uri object is created by passing in the URL from line 9. As this is a normal let expression, this is done synchronously.
    2. A WebClient object is created synchronously on line 13 with the normal let keyword.
    3. On line 14, a URI is downloaded asynchronously by using let! html = webClient.AsyncDownloadString(uri). At this point the current thread does not block, processing resumes at line 15 when the AsyncDownloadString function completes asynchronously.
    4. Line 16 simply prints out the details of an exception if one occurs.
  • The final function let runAll() on line 20 is used to create to compose the asynchronous workflow, this can be summarised in the following steps:
    1. The urlList on line 21 is passed into the Seq.map function on line 22 along with fetchAsync. This results in the async workflow being built but not yet executed, which is the same lazy evaluation principal used in LINQ.
    2. Each resulting asynchronous workflow produced by Seq.map is passed to Async.Parallel, which distributes the work to the available cores once the asynchronous workflow is executed.
    3. Finally the asynchronous workflow is executed with Async.RunSynchronously, which will execute the Asynchronous workflow in parallel and wait for the result.

Its important to note that runAll is a function, and that no asynchronous operation is actually start until line 27 when the runAll() function is invoked. This method of expressing asynchronous operations allows you to compose complex actions into small succinct blocks of code.

In the .NET Framework the IAsyncResult pattern is the standard way of writing asynchronous calls with its BeginXxx and EndXxx based method calls. To aid in the reuse of a existing functionality in the .NET framework, there are various primitives present in F#’s Async module that allow you to wrap the Begin/End methods, this allows you to take full advantage of the asynchronous workflows. For example you could use something like this:

Async.FromBeginEnd(ws.BeginGetWeather, ws.EndGetWeather)

The Async.FromBeginEnd function effectively wraps an standard asynchronous call to a web service, calling the Begin/End methods when the asynchronous operation completes.

Asynchronous workflows allow you to write simple blocks of code that effectively wrap complex asynchronous functions and callbacks. This feature of F# was one of the main reasons I started to learn the language.

Final thoughts

Using functional programming requires quite a change in mindset when you come from an imperative programming background; it requires you to think functionally about problems rather than imperatively, thinking of the overall outcome of a functions rather than a series of steps. I found that once I got over the initial learning curve it is an absolute pleasure and joy to work with, you can produce succinct code that is both expressive and performant. I have found this especially useful in the server domain where asynchronous operations and IO can dominate heavily. I am currently using F# to develop an open source high performance socket and IO pipeline library.

If you are coming from an imperative background and want to find out more about F# and functional programming then there’s an excellent book by Tomas Petricek and Jon Skeet Real-World Functional Programming With examples in F# and C#. This book shows side-by-side examples of F# and C# code and can give you a real flavour of what can be achieved with F# and how it is comparable with C# code.

You might also like...

Comments

About the author

Dave Thomas

Dave Thomas United Kingdom

Dave Thomas is a technologist, computer programmer, and keen guitarist. He builds reliable efficient, high performance enterprise software in F# and C#, and also likes to belt out a good riff o...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“There's no test like production” - Anon