F#struct成员引用'this'会导致错误

Car*_*iel 9 f# struct class this seq

新的F#开发人员,长期以来的C#开发人员.作为学习F#的练习,我正在通过Eric Lippert关于图形着色的系列工作,从C#转换为F#.我目前正在做第二部分.

最初的C#在博文中 - 这是迄今为止的F#翻译 - 但是它没有编译:

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
Run Code Online (Sandbox Code Playgroud)

这会产生非常神秘的错误"错误FS0406:byref-typed变量'this'以无效方式使用.Byrefs不能被闭包捕获或传递给内部函数"来自Bits:seq <int>的定义.

奇怪的是,将关键字"struct"更改为"class"会产生有效的代码.从C#的角度来看,这似乎是胡说八道,但我确信它背后有一个合理的原因.现在的问题是-如何我写的位功能?为理解这一点,我需要理解的基本F#原则是什么?

Tom*_*cek 9

我认为问题是this引用被创建为对结构的当前值的引用,因此您可以修改结构(如果您需要并且结构是可变的).

这会导致内部问题,seq { .. }因为这会在另一个类中生成代码,因此编译器无法将引用传递给"this"实例.

如果您为this一个普通的局部变量赋值,那么代码工作正常:

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

作为一种风格问题 - 我可能会使用隐式构造函数,这会使代码更短.我也更喜欢Struct显式struct .. end语法的属性,但这两者只是风格问题(我相信其他人会有不同的偏好).您可能会发现它只是作为替代或比较有用:

[<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
        }
Run Code Online (Sandbox Code Playgroud)


Eri*_*ert 9

为了解决你的观点:

这会产生非常神秘的错误"错误FS0406:byref-typed变量'this'以无效的方式使用.Byrefs不能被闭包捕获或传递给内部函数"来自Bits:seq的定义.奇怪的是,将关键字"struct"更改为"class"会产生有效的代码.从C#的角度来看,这似乎是无稽之谈

从C#的角度来看,这似乎不是废话.这里:

struct Mutable
{
  public int x;
  public void DoIt()
  {
    Action a = () => {
      this.x = 123;
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

您将在该程序中得到相同的错误,以及有用的建议,您可以通过将其复制到本地来捕获"this"值.

这是三个事实的结果:首先this在结构中S是类型ref S,而不是S,第二,捕获变量,而不是值,第三,.NET类型系统不允许长期存储ref变量存储池,又称GC'd堆.Refs只能用于短期存储池:堆栈或寄存器.

这三个事实共同意味着你不能this以任何可以比激活更长时间存活的方式存储在结构中,但这正是我们在创建委托时所需要的; 代表在长期池中.