【问题标题】:How to declare an immutable graph with circular references?如何使用循环引用声明不可变图?
【发布时间】:2023-03-15 13:59:01
【问题描述】:

我想声明一个所有状态的图,其中边缘表示连续状态。我认为我正在尝试做的事情可能被称为“打结”(虽然不确定)。它没有像我预期的那样工作,我有几个问题。

首先,我想要一个包含字符串名称和连续状态列表的 State 类型。但是这个声明给出了编译器错误“...立即循环引用...”:

type State = string * (State list)

这种方式有效:

type State(name:string, contigs: (State list)) = 
  let name = name
  let contigs = contigs

但实际上并不需要为成员命名。一个元组很好。我怎样才能使这种简洁的语法起作用?

其次,以下代码尝试声明应该是三个连续状态图(HI 和 AK 是由单个节点组成的图,所有剩余状态构成最后一个图),然后是所有节点的列表。 (为简洁起见,我实际上只在这里声明了少数几个州):

let rec hi = State("hi", []) 
and mo = State("mo", [il ia]) 
and il = State("il", [mo]) 
and ia = State("ia", [mo])
and states = [hi,mo,il,ia]

这会产生各种错误,包括“mo 最终将作为它自己定义的一部分进行评估”和“表达式应该具有类型 'a->'b 但这里有类型 State”。我认为 'rec' 和 'and' 关键字会允许这个工作。我可以定义这个自引用图吗?如果有,怎么做?

【问题讨论】:

    标签: f# circular-reference


    【解决方案1】:

    问题在于您的数据结构和使用了无效的列表元素分隔符(应该是分号)。 这行得通: (见编辑)

    type State =
      | State of string * State list
    
    let rec hi = State("hi", []) 
    and mo = State("mo", [il; ia]) 
    and il = State("il", [mo]) 
    and ia = State("ia", [mo])
    let states = [hi; mo; il; ia]
    

    递归引用将被具体化为 thunk (lazy)。所以你可以用mutablelazys 自己做同样的事情——仅供参考——你所拥有的是惯用的。

    编辑

    Intellisense 没有问题,但编译器说

    递归值不能在递归绑定中直接显示为“List`1”类型的构造。此功能已从 F# 语言中删除。考虑改用记录。

    您可以使用seq 而不是list 来解决此问题。

    type State =
      | State of string * State seq
    
    let rec hi = State("hi", []) 
    and mo = State("mo", seq { yield il; yield ia }) 
    and il = State("il", seq { yield mo }) 
    and ia = State("ia", seq { yield mo })
    let states = [hi; mo; il; ia]
    

    【讨论】:

    • 一旦你允许序列,它就不再是不可变的了。 "'a seq" 只是 IEnumerable 的别名。这包括数组和可变列表。
    【解决方案2】:

    尽管 Daniel 所说的是正确的,但我会质疑它“惯用”的断言,因为在一般情况下,这不会产生一个非常有用的数据结构来表示图形。具体来说,它只允许从它们添加新的顶点和边,但不允许在现有顶点之间添加或删除边。特别是,这基本上意味着您的图表必须在源代码中静态定义为常量,这样您就不能轻松地从磁盘加载这样的图表。

    图形的惯用纯函数表示是用字典查找替换取消引用。例如,将图表示为从顶点到有边的顶点的Sets 的Map

    > let g =
        Map["hi", set[]; "mo", set["il"; "ia"]; "il", set["mo"]; "ia", set["mo"]];;
    val g : Map<string,Set<string>> =
      map
        [("hi", set []); ("ia", set ["mo"]); ("il", set ["mo"]);
         ("mo", set ["ia"; "il"])]
    

    例如,您可以像这样从mo 查找可通过边直接到达的顶点:

    > g.["mo"];;
    val it : Set<string> = set ["ia"; "il"]
    

    这比可变表示更容易调试,但它有很大的缺点:

    1. 在像 Map 这样的纯函数式字典中查找至少比取消引用遍历图的指针慢 200 倍(根据此处的快速测试)。

    2. 垃圾收集器不再为您回收无法访问的子图。命令式解决方案是使用弱字典,但没有已知的纯功能弱字典。

    所以这只有在性能和泄漏不会成为问题的情况下才可行。当您的图表很小或静态时,这种情况最常见。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-01
      • 2021-12-22
      • 2012-08-29
      • 1970-01-01
      • 2014-06-06
      • 1970-01-01
      • 2011-11-14
      相关资源
      最近更新 更多