DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Brian Berns
Brian Berns

Posted on

F# Tip 1: Don't use classes

If you're coming to F# from an object-oriented language like C#, you might be tempted to continue encapsulating data and methods in classes. It's true that F# supports classes just fine, but you're really better off not using them unless you really need to (e.g. for interop with C#).

For one thing, it's tempting to make classes mutable, which is a sure path back to the dark side. But on top of that, classes come with a lot of complexity that is usually not needed: constructors, interfaces, properties, fields, methods, virtual, static, protected, private, internal... Ugh. Free up all that mental space so you can instead use it to learn cool stuff like workflows and type providers.

Don't worry, you can continue to encapsulate your data and functions in a single file, like this:

// Car.fs
namespace Tip

type Color = Red | White | Black

type Car =
    {
        Mileage : int
        Color : Color
    }

module Car =

    let create color =
        { Mileage = 0; Color = color }

    let drive miles car =
        { car with Mileage = car.Mileage + miles }

    let paint color car =
        { car with Color = color }
Enter fullscreen mode Exit fullscreen mode

First we define an immutable record type called Car. Then we define a module that contains Car-based functions. You can create a new car in any color, drive it down the road for a few miles, and even change its paint job. If you compare this code to the corresponding C# or F# class, I think you'll agree that it's simpler and just as effective.

Top comments (2)

Collapse
 
rickbradford profile image
RickBradford • Edited on

A hybrid way of doing this is to use type extensions. The golden rule is that you keep everything immutable, but streamline the code somewhat.

The example above would be written as :

type Car =
   private 
    {
        Mileage : int
        Color : Color
    }
    static member create color = { Mileage = 0; Color = color }
    member this.drive miles = { this with Mileage = car.Mileage + miles }
    member this.paint color = { this with Color = color }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
shimmer profile image
Brian Berns • Edited on

Yes, type classes are definitely a cool way to support "dot" notation in F#. You can even have it both ways by defining a plain record type with a corresponding module of functions, as I did above, and then adding type extensions at the end:

type Car with
   member this.Drive(miles) = this |> Car.drive miles
   member this.Paint(color) = this |> Car.paint color

Usage:

car |> Car.drive 10   // F# style
car.Drive(10)         // C# style

BTW, when I do this, I usually use C# naming conventions for the type extensions: capitalize the first letter and put parens around the arguments, since they can't be curried (e.g. this.Drive(miles) instead of this.drive miles).

Layoffs: It’s Okay to Not Be Okay