Last time I posted a “gotcha” snippet, asking what this F# code prints:
type MyVector(x:int, y:int, z:int) =
// expose the values as an array
member this.Values = [| x; y; z |]
let v = new MyVector(1,2,3)
v.Values.[0] <- 42
printfn "%d" v.Values.[0]
It prints “1”. To see why, let’s look at the corresponding C# code, in all its verbosity:
class MyVector
{
int x, y, z;
public MyVector(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
// expose the values as an array
public int[] Values
{
get { return new[] { x, y, z }; }
}
}
class Program
{
static void Main(string[] args)
{
var v = new MyVector(1, 2, 3);
v.Values[0] = 42;
System.Console.WriteLine(v.Values[0]);
}
}
The C# code, being more explicit, makes things a little more obvious. “Values” is a getter property, whose body creates a new array, and so every time you refer to “.Values”, a fresh array is created that contains the 3 integers that were passed into the constructor. So the line that assigns the value 42 assigns it into a fresh array that is then immediately dropped on the floor.
This probably isn’t what is desired. If we want “Values” to expose a single instance of a mutable array, then we can easily do so, by creating that array instance in the class constructor and returning that same instance each time people refer to the property. In C#:
class MyVector
{
int[] a;
public MyVector(int x, int y, int z)
{
this.a = new[] { x, y, z };
}
// expose the values as an array
public int[] Values
{
get { return a; }
}
}
class Program
{
static void Main(string[] args)
{
var v = new MyVector(1, 2, 3);
v.Values[0] = 42;
System.Console.WriteLine(v.Values[0]);
}
}
and in F#:
type MyVector(x:int, y:int, z:int) =
let a = [| x; y; z |]
// expose the values as an array
member this.Values = a
let v = new MyVector(1,2,3)
v.Values.[0] <- 42
printfn "%d" v.Values.[0]
I see people occasionally stumble on this in a variety of ways; the fact that F# property getter syntax (and overall syntax) is so succinct makes it easier to forget that the body of the member is code that will run each time the member is referenced. One-time initialization should be moved to the constructor (the let/do section prior to the members; see this blog for a primer on the F# class/interface syntax).
Every language has its own little gotchas; at work those on the C# team still fall into the trap described here, and so it is fun to josh each other about the minor warts in our respective languages. :)