对于我在Haskell中的矢量图形库,我必须携带一个相当大的状态:行笔画参数,颜色,剪辑路径等。我知道两种这样做的方法。引用来自Haskell-cafe的评论:“我建议你要么使用具有可变状态的读取器monad,要么使用具有不可变状态的状态monad”。在Haskell中快速更新大状态
这是我的问题:更新一个大的不可变状态是一个性能杀手。使用大量STRefs就像在Haskell中编写C:它是冗长而丑陋的。
这里是不可改变状态:
data GfxState = GfxState {
lineWidth :: Double,
lineCap :: Int,
color :: Color,
clip :: Path,
...
}
setLineWidth :: Double -> State GfxState()
setLineWidth x = modify (\state -> state { lineWidth = x })
据我所知,“状态{=的lineWidth X}”创建一个新的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`?以更实用的方式制作界面将使实现更具功能性。 – luqui 2010-11-24 10:55:25
对于第一个版本,使用“state {lineWidth = x}”更新状态应该与旧状态共享,所以我不希望它创建一个全新的状态。您可能希望至少使状态的“原子”元素变得严格(例如,lineWidth变为!Double,并且lineCap变为!Int),我怀疑这可能会妨碍性能。 – 2010-11-24 11:59:11
@stephen,以及*值*与旧记录共享。但是如果你有一个有100个字段的记录,每个记录更新将复制100个指针。 – luqui 2010-11-24 12:42:11