如何创建缓存的递归类型?

时间:2017-08-09 08:32:37

标签: recursion types f#

open System
open System.Collections.Generic

type Node<'a>(expr:'a, symbol:int) = 
    member x.Expression = expr
    member x.Symbol = symbol
    override x.GetHashCode() = symbol
    override x.Equals(y) = 
        match y with 
        | :? Node<'a> as y -> symbol = y.Symbol
        | _ -> failwith "Invalid equality for Node."

    interface IComparable with
        member x.CompareTo(y) = 
            match y with
            | :? Node<'a> as y -> compare symbol y.Symbol
            | _ -> failwith "Invalid comparison for Node."

type Ty =
    | Int
    | String
    | Tuple of Ty list
    | Rec of Node<Ty>
    | Union of Ty list

type NodeDict<'a> = Dictionary<'a,Node<'a>>

let get_nodify_tag =
    let mutable i = 0
    fun () -> i <- i+1; i

let nodify (dict: NodeDict<_>) x =
    match dict.TryGetValue x with
    | true, x -> x
    | false, _ ->
        let x' = Node(x,get_nodify_tag())
        dict.[x] <- x'
        x'

let d = Dictionary(HashIdentity.Structural)
let nodify_ty x = nodify d x

let rec int_string_stream = 
    Union 
        [
        Tuple [Int; Rec (nodify_ty (int_string_stream))]
        Tuple [String; Rec (nodify_ty (int_string_stream))]
        ]

在上面的示例中,int_string_stream给出了类型错误,但它巧妙地说明了我想要做的事情。当然,我希望双方都在symbol中使用相同的nodify_ty标记。当我尝试将Rec类型更改为Node<Lazy<Ty>>时,我发现它没有正确地比较它们,并且每一方都得到一个对我来说无用的新符号。

我正在研究一种语言,到目前为止我处理存储递归类型的方法是将Rec映射到int,然后用相关的{{1}替换它在我需要的时候在字典中。目前,我正在清理语言,并希望Ty案例为Rec,而不是Node<Ty>

此时此刻,我不确定我还能在这里尝试什么。这可以以某种方式完成吗?

1 个答案:

答案 0 :(得分:2)

我认为你需要在代表你的类型的歧视联盟中添加某种形式的明确“延迟”。如果没有明确的延迟,您将始终完全评估类型,因此没有可能关闭循环。

这样的事似乎有效:

type Ty =
    | Int
    | String
    | Tuple of Ty list
    | Rec of Node<Ty>
    | Union of Ty list
    | Delayed of Lazy<Ty>

// (rest is as before)

let rec int_string_stream = Delayed(Lazy.Create(fun () ->
    Union 
        [
        Tuple [Int; Rec (nodify_ty (int_string_stream))]
        Tuple [String; Rec (nodify_ty (int_string_stream))]
        ]))

这意味着当您在Ty上进行模式匹配时,您将始终需要检查Delayed,评估延迟值,然后再次模式匹配,但这可能是可行的!