在 F# 中制作一个可变结构,与 class

making a mutable struct, in F#, vs. a class

让我们想象一下这个递归函数:

let rec doSomething dataList b c d e f g h i =
    // go through dataList and recurse with updated parameters

所以我们可以让它更干净:

type Context = { b:..; c:..; ... }
let rec doSomething dataList context =
    // recurse with an updated context object

实际上,这会比较慢,因为每次都必须重新创建上下文对象,并且使用非常紧凑的循环和简单的计算,开销是可见的。

我有几个这样的数学循环,但现在我有一个新的场景:

我需要将它们用于通过使用相同的逻辑流入的数据,但是随着数据的到来逐步进行。

所以我的函数应该是这样的:

let doOneStepOfSomething oneData context : result * Context =
    process one piece of data and returns the context object for the next iteration

但是我也遇到了重新创建对象的问题,我需要保留不同的上下文。

对于每个实例都可以将其上下文变量设为可变的 class,这是一个有效的用例吗?或者为上下文创建一个带有可变字段的可变类型是否有意义?

对于任何与性能相关的问题,唯一的答案是您必须对其进行测试才能看到。我使用一个简单的函数和 #time 特性测试了四种不同的方法。

不可变功能记录

type C = { a:int; b:int; c:int; d:int }

let rec foo c = 
  if c.a > 100000000 then c
  else foo { a = c.a + 1; b = c.a; c = c.a; d = c.a }

foo { a=0; b=0; c=0; d=0 } // ~1.1 sec

带内联参数的函数

let rec bar a b c d = 
  if a > 100000000 then (a,b,c,d)
  else bar (a+1) a a a 

bar 0 0 0 0 // ~200ms

使用不可变结构替换记录

[<Struct>]
type S(a:int,b:int,c:int,d:int) = 
  member x.A = a
  member x.B = b
  member x.C = c
  member x.D = d

let rec goo (c:S) = 
  if c.A > 100000000 then c
  else goo (S(c.A+1, c.A, c.A, c.A))

goo (S(0,0,0,0)) // ~1.6sec

使用可变记录

type M = { mutable a:int; mutable b:int; mutable c:int; mutable d:int }

let rec zoo c = 
  if c.a > 100000000 then c
  else 
  c.a <- c.a + 1
  c.b <- c.a
  c.c <- c.a
  c.d <- c.a
  zoo c

zoo { a=0; b=0; c=0; d=0 } // ~1 sec

从这四个测试来看,内联参数似乎比其他任何东西都更有效(几乎都是一样的)。所以我只使用不可变记录,它使代码清晰,只有当它在你的系统中被证明是一个更普遍的问题时才优化它。