如何提升State Monad以聚焦记录字段?能否用Lens实现转换?
使用Lens实现State类型的聚焦转换
完全可以用Lens实现你要的someMagicFunc,核心就是用Control.Lens里的zoom函数。
步骤说明:
定义Circle到Point的Lens:
因为Circle包含center字段,我们需要一个能聚焦到该字段的Lens。可以手动定义,也用TemplateHaskell自动生成:手动定义Lens:
import Control.Lens (Lens', zoom) import Control.Monad.State (State, get, put) data Point = Point (Double, Double) deriving (Show) data Circle = Circle { center :: Point , radius :: Double } deriving (Show) -- 手动定义聚焦到Circle的center字段的Lens centerLens :: Lens' Circle Point centerLens f (Circle c r) = fmap (\newCenter -> Circle newCenter r) (f c)用TemplateHaskell自动生成:
如果导入Control.Lens.TH,可以通过命名约定自动生成Lens,更便捷:import Control.Lens (Lens', zoom, makeLenses) import Control.Monad.State (State, get, put) data Point = Point (Double, Double) deriving (Show) data Circle = Circle { _center :: Point -- 字段名加下划线前缀 , _radius :: Double } deriving (Show) makeLenses ''Circle -- 自动生成center :: Lens' Circle Point
实现someMagicFunc:
你要的someMagicFunc其实就是zoom结合上述Lens,它的类型完全符合你的需求:-- 用手动定义的centerLens someMagicFunc :: State Point a -> State Circle a someMagicFunc = zoom centerLens -- 如果用自动生成的Lens,直接写 -- someMagicFunc = zoom center
原理说明
zoom函数的作用就是将作用于子结构的State操作,提升为作用于包含该子结构的父结构的State操作。在这里,它把操作Point的State Point blahblah,转换成操作Circle的State Circle blahblah,自动帮你处理父结构的拆包和重新打包——执行时会取出Circle的center字段,运行原State操作修改它,再把修改后的center放回原Circle中。
使用示例
假设我们有一个修改Point坐标的操作:
someFuncOverPoint :: State Point () someFuncOverPoint = do Point (x, y) <- get put $ Point (x + 1, y + 1)
用someMagicFunc转换后,就能直接作用于Circle:
-- 测试代码 import Control.Monad.State (execState) testCircle :: Circle testCircle = Circle (Point (0,0)) 5.0 main :: IO () main = print $ execState (someMagicFunc someFuncOverPoint) testCircle -- 输出:Circle {_center = Point (1.0,1.0), _radius = 5.0}
内容的提问来源于stack exchange,提问作者pe200012
相关产品推荐
相关产品推荐

