Skip to main content

C# F# - different tones of .NET

I like good programming languages and C# is definetely one of them. It is my main tool at work. But it is not the only language in the world. When I've got a chance to make one of our next services in F# i was more than just happy about it. Some parts (.csprojects) were already done and needed to be replaced by their F# implementation. I thought F# is just another language with "strange" syntax which does the same stuff the same way. Boy, was I wrong. I had trouble to write interface method, then to implement it. Then to properly use other method. This is so much different. For example, in C# you declare everything about method in first line:

string method( string param1, int param2)
while in F# it will look more like this:

let method param1 param2 : string
See there is no parameter type? You don't have to add that. You can of course:

let method param1:string param2:string : string
But the thing is you don't have to. F# is static language just like C#, except it will check if types are used correctly during compilation. Maybe not exactly correct but my simplified understanding is that by default all parameters are generic. Output type can be generic too. That means we can have function such as this:

let add param1 param2 = param1 + param2
This line defined a function that adds two things. If you run it with two integers - it will add integers, if you run it with strings, it will add strings. If one of the parameters will be number and second one will be string - you get compilation error. Fair enough.
My first thing was to write implementation of single interface method:

public interface IConfigurationApi
{
        Task GetAsync(string path);
}
In F# Task<> is possible to do, but there is another way of doing async calls. As this is quite new project, I could still change the interface without adding much work:

type IConfigurationApi = 
    abstract member GetAsync : path: string  -> Async

//implementation below
type ConfigurationService() =
    
    interface IConfigurationApi with
        member this.GetAsync path = 
             let ......
In F# interface we implement is declared while implementing concrete methods, unlike in C# where we declare it in class declaration. What surprised me were the scopes. If we combine two facts:
  1. expressions have values and almost everything is an expression
  2. by adding few spaces in code, we create a little more local scope (which is an expression too)
we can do very nice things. Say we need to call some service and set variable to result of that call.

let result =                   //1
        let mutable p = 0      //2
        for i = 1 to 10 do     //3
            p <- p + i      //4
         p                     //5
    Console.WriteLine result   // prints 55
Quick explanation: I want to assign variable "result" with some computed value (1). The computations will happen in lines 2-5 which have all bigger indentation as are "more local". I will use temporary variable "p" (2). I will then do my computations (3,4) and return the computed value to the "less local" scope(5) which happens to be the assignment in line 1. Very nice.
There are some traps related to F# too. If you start an asp.net mvc project, you'll find some default methods for configuring your site:

// This method gets called by the runtime. Use this method to add services to the container.
    member this.ConfigureServices(services: IServiceCollection) =
        // Add framework services.
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) |> ignore

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    member this.Configure(app: IApplicationBuilder, env: IHostingEnvironment) =
        if (env.IsDevelopment()) then
            app.UseDeveloperExceptionPage() |> ignore
        else
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts() |> ignore

        app.UseHttpsRedirection() |> ignore
        app.UseMvc() |> ignore
You might get into strange problems after you extend them. For example take the seemingly innocent line:

 member this.ConfigureServices(services: IServiceCollection) =
        // Add framework services.
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) |> ignore
//add line below:
        services.AddSwaggerGen()
It should just add Swagger, right? Wrong, the method will not be called at all anymore. Why is that? You see, ConfigureServices is special method which needs to have specific signature. It needs to return void (or "unit" in F#). But method signature is not declared, it is computed from code that's inside. And after adding new line, last expression (which is like a "return" statement) returns IServiceCollection, which changes method signature. This is tricky because project will compile, it will just not configure itself. To fix the situation all you need to do is to add "|> ignore" at the end of the last line (just like in lines automatically generated).

Or say, you want to deserialize some service output into your object to get property value. You define your type for deserialization

type ObjectWithStatus= {StatusName : string}
and use some custom method to get result

 let data = api.GetResource()
Now, F# doesn't allow nulls in normal situations, the type ObjectWithStatus will not allow nulls. But it might be that the api class will return null (for example, it might be defined in c# project). To make things worse, you cannot just compare "data" to null (as type is defined not to allow nulls, compiler won't let you do that). We have strange situation now, nulls are forbidden, not possible even to test against and yet you can get them (this is example from real life project). To make it all work we should cast our object to obj in order to compare

if data :> obj <> null
then ......
Elegant? not so much. But works fine.
There are many traps for C# developer making first steps in F#. But it is totally worth it. Do yourself a favor and learn F#. Oh, I've found some books to be very helpful in the most frustrating moments. There is one I've referred to the most. "Get Programming with F#: A guide for .NET developers" by Isaac Abraham. If I had to recommend one book, that would be this one. Go create something.

Comments