繁体   English   中英

F#引用单元格-行为类似于指针vs静态变量

[英]F# ref cell - behave like pointer vs static variable

我正在阅读有关Ref细胞的 F#Wikibooks。

它说F#中的ref单元类似于C ++中的指针,但是从另一个示例来看,它似乎更像是一个静态变量。

以下两个功能在实际存储空间方面有何不同?

// calling incrPointer() always return 1
let incrPointer() =
    let counter = ref 0
    counter := !counter + 1
    !counter

// calling incrStatic() return 1, 2, 3, ... successively
let incrStatic = 
    let counter = ref 0
    fun () ->
        counter := !counter + 1
        !counter

另外,如何理解以下功能:

// calling incrNoPara always return 1
let incrNoPara = 
    let counter = ref 0
    let f =
        counter := !counter + 1
        !counter
    f

谢谢大家!

您询问了ref单元,但是真正引起混淆的原因是,您还不清楚如何在F#中定义函数与如何定义值之间的区别。 它们看起来都非常相似,因为它们都是用let定义的,但是值没有参数,而函数至少具有一个参数(可能是() ,即所谓的unit类型)。 这是区别的简要说明:

// This is a value
let foo = 5

// This is a function
let bar x = 5

如果在代码中写入printfn "%A" foo ,则输出中将显示5 但是,如果您编写printfn "%A" bar ,则会看到类似<fun:it@3-1> ,因为bar是一个等待进一步输入的函数。 (它忽略其参数并始终返回5,但它仍然是一个函数)。

值和函数也可以用代码块定义:

// This is still a value
let foo =
    printfn "This will be executed just once"
    5

// This is still a function
let bar x =
    printfn "This will be executed every time bar is called"
    5

您需要知道的另一件事是,当ref关键字出现在代码行中时,在执行该行时会创建一个新的ref单元。 如果该行出现在函数中,则每次调用该函数时都会创建一个新的引用单元。 但是,如果该行出现在值中,则它将在程序的生命周期内仅执行一次。

现在,让我们看一下让您感到困惑的三个代码块:

let incrPointer() =
    let counter = ref 0
    counter := !counter + 1
    !counter

该函数每次被调用时,都会定义一个新的ref单元格,该单元格包含0,将其递增并返回其值。 但是,当再次调用该函数时,将再次执行ref 0行,从而定义另一个包含0的新ref单元。现在无法访问原始ref单元,并且下次GC运行时将对其进行垃圾回收。 (并且一旦返回第二次incrPointer运行,它的ref单元也是不可访问的,并且将来还会被垃圾回收。)

let incrStatic = 
    let counter = ref 0
    fun () ->
        counter := !counter + 1
        !counter

另一方面,这是一个包含未命名函数的命名值。 当执行let incrStatic = ...代码块时,它首先创建一个ref单元,并将其分配给名称counter (该块外部不可访问)。 然后,使用fun () -> ...语法创建一个未命名的函数。 由于它使用counter ref单元,因此它保留对该单元的引用,因此,只要仍可访问此功能,垃圾收集器就不会收集该counter单元。 并且由于该函数是let incrStatic = ...块中的最后一个表达式,因此它成为incrStatic的值。 因此,现在名称incrStatic指的是一个函数,该函数每次被调用时都会增加相同的引用单元格,因此您将看到它返回的值每次都增加1。 但是该引用单元仅创建了一次。

您询问的第三个块是:

let incrNoPara = 
    let counter = ref 0
    let f =
        counter := !counter + 1
        !counter
    f

您称它为一个函数(“如何理解下面的函数”),但是这里没有函数。 此块中的两个let表达式都没有参数,因此它们都是定义值。 这里发生的是:

  1. 声明了名称incrNoPara ,它将是一个值,因为它也没有参数。 它的值将通过执行let incrNoPara = ...表达式内的代码块来计算。
  2. 在该表达式内部,创建了一个名为counter的引用单元,并将其设置为0。
  3. 现在声明了名称f ,由于它也没有参数,因此它也是一个值。
  4. let f = ...块内,名为counter的ref单元递增。 由于此名称是在封闭的代码块中声明的,因此它位于f块的范围内,因此访问它没有问题。
  5. 接下来,通过!counter表达式访问counter的值(现在是1,因为它首先被创建为0,然后仅增加一次),该表达式返回1。因为这是let f = ...的最后一个表达式let f = ...块,这也成为f的值。 因此,现在f已定义为值1。
  6. 现在,检索f的值(我们刚刚将其设置为1)。 由于这是let incrNoPara =块中的最后一个值,因此它成为incrNoPara的值。 而且,由于counter ref单元将超出范围,并且现在没有其他代码对其进行引用,因此它将被标记为垃圾,并最终被垃圾收集。

因此,现在将incrNoPara设置为值1,这整个代码块已成为编写let incrNoPara = 1一种复杂方法(但是创建了一个额外的ref单元,该单元必须在某个时候进行垃圾收集)。

另一方面,如果let f = ...行仅是let f () = ... ,那么您将拥有以下内容:

let incrWithInnerPara = 
    let counter = ref 0
    let f () =
        counter := !counter + 1
        !counter
    f

在这里,正在发生的事情如下:

  1. 声明了名称incrWithInnerPara ,它将是一个值,因为它也没有参数。 它的值将通过执行let incrWithInnerPara = ...表达式内的代码块来计算。
  2. 在该表达式内部,创建了一个名为counter的引用单元,并将其设置为0。
  3. 现在声明了名称f ,因为它具有参数,所以它将是一个函数。 (其参数是() ,是unit类型的唯一实例)。 函数的主体将由let f () = ...块内部的内容定义。
  4. let f () = ...块内,名为counter的ref单元递增。 由于此名称是在封闭的代码块中声明的,因此它在f函数的范围内,因此访问它没有问题。
  5. 接下来,可通过!counter表达式访问counter的值,该表达式返回其后增量值。 第一次调用f() ,该值将为1,但由于可能多次调用f() ,因此每次调用f()时该值将有所不同。 另外,由于这是let f () = ...块中的最后一个表达式,因此它也成为f函数的返回值。 因此,现在f已定义为递增计数器然后返回其新值(后递增)的函数。
  6. 现在检索f的值(我们刚刚将其定义为一个函数)。 由于这是let incrWithInnerPara =块中的最后一个值,因此它成为incrWithInnerPara的值。 同样, counter ref单元将超出范围,并且现在没有其他代码对其进行引用。 但是 f函数仍然具有对它的引用,并且由于f函数现在是incrWithInnerPara的值,因此外部代码仍然可以使用一种方法来访问counter的值。 因此, counter ref单元将不会被标记为垃圾,也不会被垃圾收集。 这称为“关闭”:函数f已“关闭” counter ref单元。 f之外的任何东西都不能访问该引用单元,但是f仍然可以访问它,并且每次调用它时都会这样做。

最后,看看如果我们简单地在incrWithInnerPara中再添加一组括号并将其incrWithOuterParaincrWithInnerPara ,将会发生incrWithOuterPara

let incrWithOuterPara () = 
    let counter = ref 0
    let f () =
        counter := !counter + 1
        !counter
    f

这次发生的事情是,每次调用incrWithOuterPara() ,它都会创建一个新的引用单元格并返回一个已关闭该新引用单元格的新函数。 因此,您可以执行以下操作:

let a = incrWithOuterPara() // Create a ref cell and returns its incr function
let b = incrWithOuterPara() // Create a *different* ref cell and returns a function
printfn "%d, %d, %d" a() a() a() // prints 1, 2, 3
printfn "%d, %d, %d" b() b() // prints 1, 2
printfn "%d" a() // prints 4
printfn "%d" b() // prints 3

我希望这可以帮助您了解发生了什么。 请随时询问有关尚不清楚的任何问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM