Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# struct member referencing 'this' results in an error

New F# developer, long time C# developer. As an exercise in learning F#, I'm working my way through Eric Lippert's series on graph coloring translating from his C# to F#. I'm currently working on part two.

The original C# is in the blog post - here's the F# translation so far - but it doesn't compile:

type BitSet = struct
        val bits : int
        private new(b) = { bits = b }

        static member Empty = BitSet(0)

        member this.Contains (item:int) = (this.bits &&& (1<<< item)) <> 0
        member this.Add (item:int) = BitSet(this.bits ||| (1 <<< item))
        member this.Remove (item:int) = BitSet(this.bits &&& ~~~(1<<<item))
        member this.Bits = seq {
                for item in 0..31 do
                    if this.Contains(item) then
                        yield item
            }
    end

This produces the very mysterious error "error FS0406: The byref-typed variable 'this' is used in an invalid way. Byrefs cannot be captured by closures or passed to inner functions" from the definition of Bits:seq<int>.

Curiously, changing the keyword "struct" to "class" results in valid code. Coming from a C# perspective, this seems like nonsense, but I'm sure there's a valid reason behind it. The question is - how should I write the Bits function? What's the underlying F# principle that I need to understand for this to make sense?

like image 750
Carl Daniel Avatar asked Jan 03 '17 21:01

Carl Daniel


1 Answers

I think the problem is that the this reference is created as a reference to the current value of the struct, so that you can modify the struct (if you wanted and the struct was mutable).

This causes problem inside seq { .. } because this generates code inside another class and so the compiler fails to pass the reference to the "this" instance.

If you assign the this value an ordinary local variable, then the code works fine:

member this.Bits = 
    let self = this
    seq {
        for item in 0..31 do
            if self.Contains(item) then
                yield item
     }

As a matter of style - I would probably use an implicit constructor, which makes the code a bit shorter. I also prefer the Struct attribute over the explicit struct .. end syntax, but both of these are just a matter of style (and I'm sure others will have different preferences). You might find it useful just as an alternative or for comparison:

[<Struct>]
type BitSet private (bits:int) = 
    static member Empty = BitSet(0)
    member this.Contains (item:int) = (bits &&& (1<<< item)) <> 0
    member this.Add (item:int) = BitSet(bits ||| (1 <<< item))
    member this.Remove (item:int) = BitSet(bits &&& ~~~(1<<<item))
    member this.Bits = 
        let self = this
        seq {
            for item in 0..31 do
                if self.Contains(item) then
                    yield item
        }
like image 153
Tomas Petricek Avatar answered Oct 19 '22 08:10

Tomas Petricek