C# vs F# – High-Level Differences in Programming Paradigms

%fcfunctional programming

I'm aware that they both use different programming paradigms, but from a high level perspective apart from differing syntax it seems most basic tasks can be achieved in similar fashion.

I only say this because when I've previously touched functional programming languages such as Haskell, writing code for basics tasks was (at first) difficult, frustrating, and required a completely different mindset.

For example the following took some time to get to grips with using recursive syntax:

loop :: Int -> IO ()
loop n = if 0 == n then return () else loop (n-1)

Where as an F# loop is recognisable and understandable almost immediately:

let list1 = [ 1; 5; 100; 450; 788 ]
for i in list1 do
   printfn "%d" i

When C# programmers start learning F# they are advised to completely re-think their thought pattern (which was definitely required for Haskell), but I've now written several F# programs dealing with conditions, loops, data sets etc to perform practical tasks, and I'm wondering where the 'different-paradigm' barrier really kicks in?

Best Answer

F# supports Object Oriented Programming but OOP isn't really idiomatic F#. OOP encourages bundling of data and behavior while functional programming encourages separating them.

Take a look at the difference between the System.Collections.Generic.List<T> and Microsoft.FSharp.Collections.List<'T> classes.

The regular (OOP) class has a large list of methods and extension methods for example, List<T>.Add(T). In contrast, the F# list has only a few properties while all of the related functions are grouped into a List module.

Let's looks at a familiar OOP example:

public interface IAnimal
{
    void Speak();
}

public class Duck : IAnimal
{
    public void Speak()
    {
        Console.WriteLine("Quack!");
    }
}

public class Cat : IAnimal 
{
    public void Speak()
    {
        Console.WriteLine("Meow!");
    }
}

You could translate that to F# (non-idiomatic):

type IAnimal = 
    abstract member Speak : unit -> unit

type Duck = 
    interface IAnimal with
        member this.Speak () -> printfn "Quack!"

type Cat = 
    interface IAnimal with
        member this.Speak () -> printfn "Meow!"

You can see that the code is shorter but not really any different. If you find yourself writing code like this, you're thinking like a C# dev :) Here's a more idiomatic translation:

type Animal = 
    | Duck
    | Cat

let speak animal = 
    match animal with 
    | Duck -> printfn "Quack!"
    | Cat -> printfn "Meow!"

Here's a alternative version of speak:

let speak = function
    | Duck -> printfn "Quack!"
    | Cat -> printfn "Meow!"

At this point, the code looks very different than the C# version largely because we have separated the behavior (speak or Speak()) from the data (in this case, just the type of Animal or IAnimal we have). One other difference is that we have expressed the possible states of data (animal kinds) in a way the compiler (and type-system) understand more intimately than the C# compiler/typesystem. Consider a function which gets the first item in a list:

public T GetHead(List<T> list)
{
    return list[0];
} 

vs:

let getHead = function
    | head :: tail -> head

Both take a idiomatic list and return the first item in it (:: is the list destructuring operator).

If you were to compile both of these, the C# version would compile with no errors or warnings and the F# version would warn you that you haven't considered all of the cases (what about the empty list?). The F# compiler realizes you haven't accounted for all possible inputs and warns you accordingly.

This is one of the other differences between the languages, as a programmer (if you have idiomatic code), you can lean on the compiler a lot more for detecting errors and bugs which C#/OOP wouldn't catch until runtime.