在F#中加载一组递归类型的元素

时间:2015-07-22 18:20:00

标签: recursion f#

假设我们在SQL数据库中有一个供应商表,我们想要加载到F#:

+----+--------+--------+
| ID |  Name  | Parent |
+----+--------+--------+
|  1 | Nest   | 2      |
|  2 | Google | NULL   |
|  3 | Apple  | NULL   |
+----+--------+--------+

使用类型提供程序很容易将表格转换为F#,但是假设我们希望将数据转换为供应商序列,其中Vendor是这样的类型:

Vendor = {ID: int; Name: String; Parent: Vendor option}

怎么会这样做呢?问题在于,在创建供应商序列时,我们无法映射到特定供应商的每一行,因为我们还没有供应商序列。最好还假设应用程序允许循环(A可以将B作为父级,B可以将A作为父级),尽管在供应商的情况下确实没有意义。

您可以将供应商类型定义为:

Vendor = {ID: int; Name: String; ParentID: int option}

但这似乎不那么优雅,因为每次你想要引用父供应商时你都必须进行某种查找。有没有一个已知的解决方案?这似乎是一种经常发生的情况(特别是在处理图形或树木时)。

似乎解决方案可能涉及某种懒惰的评估,但我不清楚Lazy<> T>。可以在此处应用F#中的类型。

1 个答案:

答案 0 :(得分:2)

它不是一个特别优雅的解决方案,但是对父级使用延迟评估的解决方案看起来像这样:你将有两种类型,一种匹配你的表的模式,一种是递归的:

type Flat = { ID: int; Name: string; ParentID : int option}

type Recursive = { ID: int; Name: string; Parent: Lazy<Recursive> option}

然后让我们设置一些看起来像你的桌子的东西:

let records = 
    [
        { ID = 1; Name = "Nest"; ParentID = Some 2 }
        { ID = 2; Name = "Google"; ParentID = None }
        { ID = 3; Name = "Apple"; ParentID = None }
        { ID = 4; Name = "Yin"; ParentID = Some 5 }
        { ID = 5; Name = "Yang"; ParentID = Some 4 }
    ]
    |> List.map (fun x -> x.ID, x)
    |> Map.ofList

let getRecord recID = records |> Map.find recID

你可以像这样把它放在一起:

let rec getRecordRecursive recID = 
    let record = getRecord recID
    {
        ID = record.ID
        Name = record.Name
        Parent = 
            record.ParentID 
            |> Option.map (fun pid -> 
                Lazy.Create <| fun () ->
                    getRecordRecursive pid)        
    }

因此,在某种意义上,您使用惰性类型来延迟递归的下一步,直到您需要它为止。否则getRecordRecursive 4会给你一个堆栈溢出。

但是存在权衡 - 例如,你不再在这些记录上获得良好的行为平等。从长远来看,我不相信你最好不要使用Flat条记录。