【发布时间】:2011-05-15 00:27:54
【问题描述】:
对于我在 Haskell 中的矢量图形库,我必须携带一个相当大的状态:线条描边参数、颜色、剪辑路径等。我知道这样做的两种方法。引用 Haskell-cafe 的评论:“我建议你要么使用状态可变的 reader monad,要么使用状态不可变的 state monad”。
这是我的问题:更新一个大的不可变状态会破坏性能。使用大量 STRef 就像在 Haskell 中编写 C 一样:冗长而丑陋。
这里是不可变状态:
data GfxState = GfxState {
lineWidth :: Double,
lineCap :: Int,
color :: Color,
clip :: Path,
...
}
setLineWidth :: Double -> State GfxState ()
setLineWidth x = modify (\state -> state { lineWidth = x })
据我所知,“state { lineWidth = x }”创建了一个新的 GfxState 并让旧的 GfxState 被垃圾回收。当状态很大并且经常更新时,这会降低性能。
这里是可变状态:
data GfxState s = GfxState {
lineWidth :: STRef s Double,
lineCap :: STRef s Int,
color :: STRef s Color,
clip :: STRef s Path,
...
many more STRefs
}
setLineWidth :: GfxState s -> Double -> ST s ()
setLineWidth state x = writeSTRef (lineWidth state) x
现在我到处都得到 (GfxState s) 和 (ST s) 和 (STRef s),这很冗长、令人困惑,并且违背了编写简短而富有表现力的代码的精神。我可以使用 C + FFI 来读取和更新大状态,但由于我经常遇到这种模式,我希望有更好的方法。
【问题讨论】:
-
做你正在做的事情就像在haskell中编写C语言一样,因为我看到你暗示的库接口是一个非常命令式的接口。
setLineWidth?使界面更具功能性的风格也会使实现更具功能性。 -
对于第一个版本,用“state { lineWidth = x }”更新状态应该与旧状态共享,所以我不希望它创建一个全新的状态。您可能希望至少使状态的“原子”元素变得严格(例如 lineWidth 变为 !Double 并且 lineCap 变为 !Int),我怀疑这可能会严重影响性能。
-
@stephen,值 与旧记录共享。但是如果你有一个包含 100 个字段的记录,那么每次记录更新都会复制 100 个指针。
-
@luqui - 确实,但这强烈建议“不要用 100 个字段创建记录……”并添加一些嵌套以将相关元素组合在一起。无论如何,从可理解性的角度来看,这将更加模块化和更好。
-
@luqui,setLineWidth 不属于接口。该接口有一个函数,它接受命令列表(moveTo、lineTo、setColor、setLineWidth、stroke 等)并生成图形对象列表(Stroke {path::Path、color::Color 等})。跨度>
标签: data-structures haskell state-monad