【问题标题】:How to create a recursive data structure value in (functional) F#?如何在(功能)F# 中创建递归数据结构值?
【发布时间】:2011-03-06 09:54:37
【问题描述】:

一个值怎么可能有类型:

type Tree =
    | Node of int * Tree list

有一个引用自身以函数方式生成的值吗?

在下面的 Python 代码中,结果值应该等于 x,以获得合适的 Tree 定义:

x = Tree()
x.tlist = [x]

编辑:显然需要更多解释。我正在尝试学习 F# 和函数式编程,所以我选择实现我之前用其他语言编写的 cover tree。这里相关的是,每个级别的点都是下一个级别的点的子集。该结构在概念上达到了无穷级。

在命令式语言中,节点有一个包含自身的子节点列表。我知道这可以在 F# 中强制完成。不,考虑到覆盖树算法,它不会创建无限循环。

【问题讨论】:

  • 对于我们这些不太会说 Python 的人来说,您能否详细说明这个问题?您的类型定义看起来不错,但请注意,F# 不允许直接构造可区分联合类型(例如,树)......仅构造联合值;例如,Node,如 "let x = Node( 0, [] )" 或 "let y = Node( 1, [x] )",其中 [] 是空列表。
  • @James:他想创建一个自己的子节点。就像let rec x = Node (something, [x]) 一样(除非那不起作用)。
  • @sepp2k:这通常会为遍历列表的任何代码创建一个无限循环,尽管...非常不受欢迎 :-) 我已经看到在其他语言中这样做以标记列表的结尾,但 idomatic F# 会为此目的使用另一个 DU 值,例如“None”。
  • 您不是尝试使用 F# 语法编写 Python 代码,而是究竟想要做什么?就其价值而言,纯粹的函数式循环数据结构是一种思维方式(参见haskell.org/haskellwiki/Tying_the_Knot),您最好使用非循环数据结构编写代码。
  • @sepp2k:谢谢,这正是我的意思。 @朱丽叶:感谢您的链接。请参阅上面编辑过的问题。

标签: f# tail-recursion recursive-datastructures


【解决方案1】:

Tomas 的回答提出了两种在 F# 中创建递归数据结构的可能方法。第三种可能性是利用记录字段支持直接递归这一事实(当在定义记录的同一程序集中使用时)。例如,以下代码可以正常工作:

type 'a lst = Nil | NonEmpty of 'a nelst
and 'a nelst = { head : 'a; tail : 'a lst }

let rec infList = NonEmpty { head = 1; tail = infList }

使用此列表类型而不是内置列表类型,我们可以使您的代码正常工作:

type Tree = Node of int * Tree lst
let rec x = Node(1, NonEmpty { head = x; tail = Nil })

【讨论】:

  • 有趣。从概念上讲,'a lst 的定义在某种意义上是否等同于惰性列表?
  • @Muhammad - 不,'a lst 并不懒惰,所以它就像内置列表一样。它通过记录的事实允许您创建自引用数据结构,但是您不能使用这种类型来执行诸如创建所有偶数的流之类的操作,就像使用惰性列表一样。
  • 谢谢。这就是我要找的。​​span>
【解决方案2】:

如果递归引用没有延迟(例如,包装在函数或惰性值中),则不能直接执行此操作。我认为动机是没有办法“立即”通过直接引用来创造价值,所以从理论的角度来看这会很尴尬。

但是,F# 支持递归值 - 如果递归引用被延迟,您可以使用这些值(然后 F# 编译器将生成一些代码来初始化数据结构并填充递归引用)。最简单的方法是将引用包装在一个惰性值中(函数也可以):

type Tree = 
    | Node of int * Lazy<Tree list>

// Note you need 'let rec' here!        
let rec t = Node(0, lazy [t; t;])

另一种选择是使用变异来编写它。然后你还需要使你的数据结构可变。例如,您可以存储 ref&lt;Tree&gt; 而不是 Tree

type Tree = 
    | Node of int * ref<Tree> list

// empty node that is used only for initializataion
let empty = Node(0, [])
// create two references that will be mutated after creation    
let a, b = ref empty, ref empty

// create a new node
let t = Node(0, [a; b])
// replace empty node with recursive reference
a := t; b := t

正如 James 所提到的,如果不允许您这样做,您可以拥有一些不错的属性,例如任何遍历数据结构的程序都会终止(因为数据结构是有限的并且不能递归)。所以,你需要对递归值更加小心:-)

【讨论】:

  • 不错的答案。谢谢。从效率的角度来看,一个比另一个更好吗? (不知怎的,我觉得我会后悔的;)
  • Lazy 严格来说比ref 更复杂(它必须跟踪它是否被评估过),所以ref 应该更有效,但我怀疑没有实际区别。
猜你喜欢
  • 2011-09-25
  • 2021-05-02
  • 2020-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-11
  • 2017-11-28
  • 2019-04-14
相关资源
最近更新 更多