5

How do F# immutable types interface with C#. I'm just starting to learn F# and I'd like to mix it in with some C# code I have, but I want my F# classes to be immutable.

Let's say we're making a Vector class in F#. Vector.X and Vector.Y should be re-assignable, but only be returning a new Vector class. In C# this would take allot of legwork to make .WithX(float x) clone the existing object and return a new one. Is there an easy way to do this in F#?

I've been searching for some time and I can't seem to find any docs on this. So any help would be great.

And finally, if I imported this class into C# what would its interface look like? Will the F# code restrict me from doing something stupid like Vector.X = 10?

Timothy Baldridge
  • 10,455
  • 1
  • 44
  • 80

2 Answers2

9

This will look similar regardless of whether it's C# or F#.

You say "in C# it will take legwork", but cmon, I think

Vector WithX(float x) { return new Vector(x, this.Y); }

is it, right?

In both C# and F#, to prevent assignment to the X property, you author a property with a 'getter' but no 'setter'.

I think you're making all of this out to be harder than it is, or maybe I'm misunderstanding what you're asking.

EDIT

For the (I think rare) case of where there are 20 field and you may want to change just a small arbitrary subset of them, I found a cute hack to use F# and C# optional parameters together nicely.

F# Code:

namespace global

open System.Runtime.InteropServices

type Util =
    static member Some<'T>(x:'T) = Some x

type MyClass(x:int, y:int, z:string) =
    new (toClone:MyClass, 
         [<Optional>] ?x, 
         [<Optional>] ?y, 
         [<Optional>] ?z) = 
            MyClass(defaultArg x toClone.X, 
                    defaultArg y toClone.Y, 
                    defaultArg z toClone.Z)
    member this.X = x
    member this.Y = y
    member this.Z = z

F# client code:

let a = new MyClass(3,4,"five")
let b = new MyClass(a, y=44)  // clone a but change y

C# client code:

var m = new MyClass(3, 4, "five");
var m2 = new MyClass(m, y:Util.Some(44)); // clone m but change y

That is, optional parameters are a nice way to do this, and while C# optional parameters have some limitations, you can expose F# optional parameters in a way that works ok with C#, as suggested above.

Brian
  • 117,631
  • 17
  • 236
  • 300
  • Well yes, this is simpler. But what if I have 20 members to my single class? In that case it's much easier to have "WithX(40)" than call the constructor. Many times I only need to modify one member out of the 20 in that class, so spelling them all out as constructor arguments gets to be a bit verbose. – Timothy Baldridge Jan 25 '11 at 20:30
  • And let's say I want to change my Vector class to accept a Z value as well, well then I have to go and modify WithX(), WithY() and the Vector constructor. – Timothy Baldridge Jan 25 '11 at 20:31
  • As Stephen points out, Record types provide this functionality. If you need classes instead, you can use a workaround such as [this one](http://stackoverflow.com/questions/2873125/f-record-member-evaluation/2885063#2885063). – Daniel Jan 25 '11 at 20:36
  • Both comments are excellent. Daniel, I never thought of using optional arguments to do this. That's excellent. – Timothy Baldridge Jan 25 '11 at 20:46
7

F# Record types have a built in way of doing exactly what you're asking:

type Vector = {X:float; Y:float}

let v1 = {X=1.; Y=2.}
let v2 = {v1 with X=3.}

How that interops with C#, I'm not sure (edit: see Brian's comment).

Vector will be immutable from any .NET language, since X and Y are implemented as getters without setters.

Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
  • 1
    The record `with` syntax is F# syntax sugar for calling the constructor with the other arguments unchanged. So there is no 'interop' since it's just syntax sugar that codegens down into the more verbose constructor call. – Brian Jan 25 '11 at 20:37