作者:仰望天空说再见 | 来源:互联网 | 2023-02-04 10:44
1> Fyodor Soiki..:
首先,你的直接问题:你定义Atts
属性的方式,它不是一个"存储"在某个地方的值,可以通过属性访问.相反,您的定义意味着"每次有人读取此属性,创建一个新字典并返回它".这就是你的新属性没有出现在词典中的原因:每次阅读时它都是一个不同的词典root.Atts
.
要创建具有支持字段和初始值的属性,请使用member val
:
type XmlNode(...) =
...
member val Atts = Dictionary()
现在,回答一些隐含的问题.
第一项业务: "修改属性"和"纯粹功能"是矛盾的想法.函数式编程意味着不可变数据.什么都没有改变.推进计算的方法是在每一步创建一个新的数据,而不是覆盖前一个.这个基本思想在实践中证明是非常有价值的:更安全的线程,琐碎的"撤销"场景,平凡的并行化,对其他机器的简单分配,甚至通过持久数据结构减少内存消耗.
不变性是非常重要的一点,我恳请你不要瞥一眼.接受它需要精神上的转变.从我自己(以及我认识的其他人)的经验来看,很难从命令式编程中获得,但它非常值得.
第二:不要使用类和属性.从技术上讲,面向对象的编程(在消息传递的意义上)与功能并不矛盾,但在C++,Java,C#等人的实践中使用的企业风格是矛盾的,因为它强调了这个想法"方法是改变对象状态的操作",这是不起作用的(参见上文).所以最好避免面向对象的结构,至少在你学习的时候.特别是因为F#提供了更好的数据编码方式:
type XmlNode = { TagName: string; InnerValue: string; Atts: (string*string) list }
(注意我Atts
的不是字典;我们稍后会谈到这一点)
同样,要表示对数据的操作,请使用函数,而不是方法:
let printNode (node: XmlNode) = (* we'll come to the implementation later *)
第三:为什么你说你"显然"需要修改属性?您展示的代码并不需要这样做.例如,使用我XmlNode
上面的定义,我可以这样重写你的代码:
[]
let main argv =
let root = { TagName = "root"; InnerValue = "test"; Atts = ["att", "val"] }
printfn "%s" (printNode root)
...
但即使这是真正的需要,你也不应该"到位".正如我在上面讨论不变性时所描述的那样,你不应该改变现有节点,而是创建一个与你想要"修改"的新节点不同的新节点:
let addAttr node name value = { node with Atts = (name, value) :: node.Atts }
在此实现中,我获取属性的节点和名称/值,并生成一个新节点,该节点的Atts
列表包含原始节点中Atts
具有前置新属性的任何内容.
原始Atts
列表保持不变,未经修改.但这并不意味着内存消耗的两倍:因为我们知道原始列表永远不会改变,我们可以重用它:我们通过仅为新项目分配内存并将旧列表的引用包括为"其他项目"来创建新列表".如果旧列表可能会发生变化,我们无法做到这一点,我们必须创建一个完整的副本(请参阅" 防御性副本 ").该策略称为" 持久数据结构 ".它是函数式编程的支柱之一.
最后,对于字符串格式化,我建议使用sprintf
而不是StringBuilder
.它提供类似的性能优势,但另外提供类型安全性.例如,代码sprintf "%s" 5
将无法编译,抱怨格式需要一个字符串,但最后一个参数5
是一个数字.有了这个,我们可以实现这个printNode
功能:
let printNode (node: XmlNode) =
let atts = seq { for n, v in node.Atts -> sprintf " %s=\"%s\"" n v } |> String.concat ""
sprintf "<%s%s>%s%s>" node.TagName atts node.InnerValue node.TagName
作为参考,这是您的完整程序,以功能样式重写:
type XmlNode = { TagName: string; InnerValue: string; Atts: (string*string) list }
let printNode (node: XmlNode) =
let atts = seq { for n, v in node.Atts -> sprintf " %s=\"%s\"" n v } |> String.concat ""
sprintf "<%s%s>%s%s>" node.TagName atts node.InnerValue node.TagName
[]
let main argv =
let root = { TagName = "root"; InnerValue = "test"; Atts = ["att", "val"] }
printfn "%s" (printNode root)
Console.ReadLine() |> ignore
0