Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Design patterns 如何添加只缓存ADT内容的字段?_Design Patterns_Caching_Haskell_Memoization_Algebraic Data Types - Fatal编程技术网

Design patterns 如何添加只缓存ADT内容的字段?

Design patterns 如何添加只缓存ADT内容的字段?,design-patterns,caching,haskell,memoization,algebraic-data-types,Design Patterns,Caching,Haskell,Memoization,Algebraic Data Types,通常,我需要在ADT中添加一些字段,这些字段只能存储一些冗余信息。但我还没有完全弄明白如何做得既好又有效 说明问题的最好方法是举个例子。假设我们使用的是非类型lambda术语: type VSym = String data Lambda = Var VSym | App Lambda Lambda | Abs VSym Lambda 有时我们需要计算一个项的自由变量集: fv :: Lambda -> Set VSym fv (Var

通常,我需要在ADT中添加一些字段,这些字段只能存储一些冗余信息。但我还没有完全弄明白如何做得既好又有效

说明问题的最好方法是举个例子。假设我们使用的是非类型lambda术语:

type VSym = String

data Lambda = Var VSym 
            | App Lambda Lambda
            | Abs VSym Lambda
有时我们需要计算一个项的自由变量集:

fv :: Lambda -> Set VSym
fv (Var v)    = Set.singleton v
fv (App s t)  = (fv s) `Set.union` (fv t)
fv (Abs v t)  = v `Set.delete` (fv t)
很快我们意识到重复计算
fv
是我们应用程序的瓶颈。我们希望以某种方式将其添加到数据类型中。比如:

data Lambda1 = Var (Set VSym) VSym
             | App (Set VSym) Lambda Lambda
             | Abs (Set VSym) VSym Lambda
但这使得定义相当难看。几乎
(Set-VSym)
比其他所有代码占用更多的空间。此外,它打破了所有使用
Lambda
的函数中的模式匹配。更糟糕的是,如果我们以后决定添加一些其他的记忆字段,我们将不得不再次重写所有模式

如何设计一个通用的解决方案,允许轻松、不引人注目地添加此类记录字段?我想达到以下目标:

  • 数据
    定义应尽可能接近原始定义,以便易于阅读和理解
  • 模式匹配也应该保持简单易读
  • 以后添加新的备忘录字段不应破坏现有代码,尤其是:
    • 不打破现有模式,
    • 在使用我们想要记忆的函数的地方不需要更改(如本例中使用
      fv
      的代码)

  • 我将描述我当前的解决方案:为了使
    数据
    定义和模式匹配尽可能少地混乱,让我们定义:

    data Lambda' memo = Var memo VSym 
                      | App memo (Lambda' memo) (Lambda' memo)
                      | Abs memo VSym (Lambda' memo)
    type Lambda = Lambda' LambdaMemo
    
    如果要记录的数据是单独定义的:

    data LambdaMemo = LambdaMemo { _fv :: Set VSym, _depth :: Int }
    
    然后是一个简单的函数,用于检索已记忆的部分:

    memo :: Lambda' memo -> memo
    memo (Var c _)   = c
    memo (App c _ _) = c
    memo (Abs c _ _) = c
    
    (这可以通过使用命名字段来消除,但我们也可以这样做。)

    这使我们能够从备忘录中选择特定的部分,保持与以前相同的
    fv
    签名:

    fv :: Lambda -> Set VSym
    fv = _fv . memo
    
    depth :: Lambda -> Int
    depth = _depth . memo
    
    最后,我们声明这些智能构造函数:

    var :: VSym -> Lambda
    var v = Var (LambdaMemo (Set.singleton v) 0) v
    
    app :: Lambda -> Lambda -> Lambda
    app s t = App (LambdaMemo (fv s `Set.union` fv t) (max (depth t) (depth s))) s t
    
    abs :: VSym -> Lambda -> Lambda
    abs v t = Abs (LambdaMemo (v `Set.delete` fv t) (1 + depth t)) v t
    
    现在我们可以高效地编写混合模式匹配和读取记忆字段的内容,如

    canSubstitute :: VSym -> Lambda -> Lambda -> Bool
    canSubstitute x s t
      | not (x `Set.member` (fv t))
          = True -- the variable doesn't occur in `t` at all
    canSubstitute x s t@(Abs _ u t')
      | u `Set.member` (fv s)
          = False
      | otherwise
          = canSubstitute x s t'
    canSubstitute x s (Var _ _)
          = True
    canSubstitute x s (App _ t1 t2)
          = canSubstitute x s t1 && canSubstitute x s t2
    
    这似乎解决了:

    • 模式匹配仍然相当合理
    • 如果我们添加一个新的记忆字段,它将不会中断现有的代码
    • 如果我们决定用签名
      Lambda->Something
      记忆一个函数,我们可以很容易地将它添加为一个新的记忆字段
    我仍然不喜欢这种设计:

    • 数据
      的定义还不错,但仍然到处放置
      备忘录
      会让它变得相当混乱
    • 我们需要有智能构造函数来构造值,但我们使用常规构造函数进行模式匹配。这并不是那么糟糕,我们只需添加一个
      ,但是具有相同的构造和解构签名就好了。我想或者会解决这个问题
    • 记忆字段(自由变量、深度)的计算与智能构造函数紧密耦合。由于可以合理地假设这些记忆化的函数将始终保持不变,我相信这可以在某种程度上通过类似的工具来解决

    有什么改进的办法吗?或者有更好的方法来解决这样的问题吗?

    我认为通过在函数中使用简单的旧记忆,而不是在ADT本身中缓存结果,可以实现您的所有目标。就在几周前,我发布了这个软件包,这在这里应该会有所帮助。检查你的标准,我认为我们没有比这更好的了:

  • 您的数据定义根本不会改变
  • 模式匹配也不会改变
  • 现有代码不必仅仅因为编写了更多的记忆函数而改变。
    • 没有任何现有模式被破坏
    • 现有的记忆功能不会被破坏
  • 使用它非常简单。只需将
    memo
    应用于任何要记忆的函数,确保在任何地方都使用该函数的记忆版本,即使在递归调用中也是如此。下面是如何编写您在问题中使用的示例:

    import Data.StableMemo
    
    type VSym = String
    
    data Lambda = Var VSym 
                | App Lambda Lambda
                | Abs VSym Lambda
    
    fv :: Lambda -> Set VSym
    fv = memo go
      where
        go (Var v)   = Set.singleton v
        go (App s t) = fv s `Set.union` fv t
        go (Abs v t) = v `Set.delete` fv t
    

    也许
    Annotations
    包(可能还有其他提供类似功能的库)会对您有所帮助?@kosmikus我简要地查看了该包,我担心如果使用这样的库,模式匹配会变得不方便。也许,如果我们将所有使用
    Lambda
    的函数表示为ana/cata态射(我一点也不介意),它们中的模式可能会变得合理。也许值得把你的评论变成一个完整的答案?很有趣。我有两个问题:(1)如果我有一个大小为
    n
    的lambda项,那么检索一个记忆结果的复杂性是什么?例如,如果使用
    地图
    对其进行记忆,那么将该术语与另一个术语进行比较需要O(n),因此检索速度会非常慢。(2) 如果我为某个lambda术语记忆
    fv
    ,然后该术语将被垃圾收集,那么记忆的数据会发生什么情况?它是被释放的还是永远留在内存中?这个术语不是由记忆函数保存在内存中吗?(1)使用
    stable memo
    检索记忆结果的复杂性平均来说是恒定的时间。该实现使用一个基于稳定名称的哈希表。(2)
    stable memo
    使用终结器确保如果前一个参数被垃圾收集,则会删除memo表中相应的条目。此外,由于memo表是在稳定名称上键入的,因此它不会不必要地保留已传递给它的任何参数。