在 Haskell 中以功能方式处理依赖项解析
Handle dependency resolution functionally in Haskell
我正在 Haskell 中实现类似于电子表格引擎的东西。
有ETables
,其中有包含AST形式的表达式的单元格行(例如BinOp + 2 2
),其中可以包含对ETables
的其他单元格的引用。
主函数应将这些 ETables
转换为 VTables
,其中包含单元格中完全解析的值(例如,单元格 BinOp + 2 2
应解析为 IntValue 4
).当单元格没有外部引用时,这非常容易,因为您可以从单元格的表达式 AST(例如 eval (BinOpExpr op l r) = IntValue $ (eval l) op (eval r)
,减去拆箱和类型检查)一直到 table (evalTable = (map . map) eval rows
)
但是,当外部引用被混入其中时,我想不出 "natural" 处理这个问题的方法。我是否正确地假设我不能只在引用的单元格上调用 eval
并使用它的值,因为 Haskell 不够聪明,无法缓存结果并在该单元格独立时重新使用它评价了?
我想出的最好的办法是使用一个逐渐填充的 State [VTable]
,因此缓存是显式的(每个 eval 调用都会在 [=36= 之前使用 return 值更新状态]ing).这应该有效,但感觉 "procedural"。是否有我缺少的更惯用的方法?
Haskell 默认情况下不会记忆,因为这通常会占用太多内存,所以你不能仅仅依靠 eval
做正确的事情。然而,惰性求值的本质意味着数据结构在某种意义上被记忆化了:大型惰性结构中的每个 thunk 仅被求值一次。这意味着您可以通过在内部定义一个大型惰性数据结构并将递归调用替换为对该结构的访问来记忆一个函数——该结构的每个部分最多将被评估一次。
我认为对电子表格建模的最优雅的方法是将单元格作为节点,将引用作为边的大型惰性有向图。然后,您需要以递归方式定义 VTable
图,以便所有递归都通过图本身,这将以我上面描述的方式记住结果。
有几种方便的方法可以为图形建模。一种选择是使用带有整数的显式映射作为节点标识符——IntMap
or even an array of some sort could work. Another option is to use an existing graph library; this will save you some work and ensure you have a nice graph abstraction, but will take some effort up front to understand. I'm a big fan of the fgl、"functional graph library",但这确实需要一些前期阅读和思考才能理解。性能不会有太大差异,因为它也是根据 IntMap
.
实现的
自鸣得意,我写了几篇博文来扩展这个答案:一篇关于 memoization with lazy structures (with pictures!) and one about the functional graph library。我相信,将这两个想法放在一起应该可以得到你想要的东西。
我正在 Haskell 中实现类似于电子表格引擎的东西。
有ETables
,其中有包含AST形式的表达式的单元格行(例如BinOp + 2 2
),其中可以包含对ETables
的其他单元格的引用。
主函数应将这些 ETables
转换为 VTables
,其中包含单元格中完全解析的值(例如,单元格 BinOp + 2 2
应解析为 IntValue 4
).当单元格没有外部引用时,这非常容易,因为您可以从单元格的表达式 AST(例如 eval (BinOpExpr op l r) = IntValue $ (eval l) op (eval r)
,减去拆箱和类型检查)一直到 table (evalTable = (map . map) eval rows
)
但是,当外部引用被混入其中时,我想不出 "natural" 处理这个问题的方法。我是否正确地假设我不能只在引用的单元格上调用 eval
并使用它的值,因为 Haskell 不够聪明,无法缓存结果并在该单元格独立时重新使用它评价了?
我想出的最好的办法是使用一个逐渐填充的 State [VTable]
,因此缓存是显式的(每个 eval 调用都会在 [=36= 之前使用 return 值更新状态]ing).这应该有效,但感觉 "procedural"。是否有我缺少的更惯用的方法?
Haskell 默认情况下不会记忆,因为这通常会占用太多内存,所以你不能仅仅依靠 eval
做正确的事情。然而,惰性求值的本质意味着数据结构在某种意义上被记忆化了:大型惰性结构中的每个 thunk 仅被求值一次。这意味着您可以通过在内部定义一个大型惰性数据结构并将递归调用替换为对该结构的访问来记忆一个函数——该结构的每个部分最多将被评估一次。
我认为对电子表格建模的最优雅的方法是将单元格作为节点,将引用作为边的大型惰性有向图。然后,您需要以递归方式定义 VTable
图,以便所有递归都通过图本身,这将以我上面描述的方式记住结果。
有几种方便的方法可以为图形建模。一种选择是使用带有整数的显式映射作为节点标识符——IntMap
or even an array of some sort could work. Another option is to use an existing graph library; this will save you some work and ensure you have a nice graph abstraction, but will take some effort up front to understand. I'm a big fan of the fgl、"functional graph library",但这确实需要一些前期阅读和思考才能理解。性能不会有太大差异,因为它也是根据 IntMap
.
自鸣得意,我写了几篇博文来扩展这个答案:一篇关于 memoization with lazy structures (with pictures!) and one about the functional graph library。我相信,将这两个想法放在一起应该可以得到你想要的东西。