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 }
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)
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 :
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:
Usage:
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 ofthis.drive miles
).