在查看了答案之后,我决定使用一个对我来说似乎最不突兀的模型。我使用了一个修改过的对象来演示它如何在稍微复杂的场景中工作。
type StackDef<'a>(v : 'a, s : Stack<'a>) =
member val Length = s.Length + 1
member val Inner = v, s
and Stack<'a> =
| Empty
| Stack of StackDef<'a>
member this.Length =
match this with
| Empty -> 0
| Stack(def) -> def.Length
let Stack (v, s) = Stack(StackDef(v, s))
let (|Stack|Empty|) = function | Empty -> Empty | Stack(sd) -> Stack(sd.Inner)
//...
let example = Stack(1, Stack(2, Stack(3, Empty))).Length
- 它不包含任何外部可变状态。
- 有区别的联合
Dish
(或在示例中为Stack
)继续存在。
- 该字段
length
根本没有出现在联合定义中,也没有由任何构造函数提供,就像它应该的那样。
- 记忆化的数据应该与实例相关联。
但是,考虑到这一点,通过使用像 Afterthought 这样的静态编织器,可能可以替换任何方法,例如:
Stack<'a> =
| Empty
| Stack of 'a * Stack<'a>
[<Lazy>] //custom attribute that would work with a static weaver
member this.Length =
match this with
| Empty -> 0
| Stack(_, s) -> s.Length + 1
private readonly Lazy<int> __length
在构造函数中使用执行上述代码的委托进行初始化,并将方法的实际内容更改为简单地调用__length.Value
. 虽然 F# 不允许联合类型包含字段,但可能出于非常正当的原因,我非常怀疑 IL 会有这样的限制。
事实上,使用一些 IL 操作可以做很多事情。也许这是值得考虑的事情。