我在较大的状态结构中使用StdGen
,并想在状态结构上实现RandomGen
类。使用镜头,我想到了以下实现:
module Test
( StateData(..)
, randomGen
) where
import Lens.Micro.Platform
import System.Random
data StateData = StateData
{ _randomGen :: StdGen
} deriving (Show)
randomGen :: Lens' StateData StdGen
randomGen = lens _randomGen (\ s x -> s { _randomGen = x })
instance RandomGen StateData where
next s = r & _2 .~ (s & randomGen .~ (r ^. _2))
where r = (s ^. randomGen ^. to next)
split s = r & _1 .~ (s & randomGen .~ (r ^. _1))
& _2 .~ (s & randomGen .~ (r ^. _2))
where r = (s ^. randomGen ^. to split)
为简化此定义(以及将来的类似定义),我想将模式概括如下:
reinsert :: (a -> b) -> Lens' s a -> [Lens b b' a s] -> a -> b'
reinsert f a bs s
= foldr (&) r [b .~ (s & a .~ (r ^. b)) | b <- bs]
where r = (s ^. a ^. to f)
instance RandomGen StateData where
next = reinsert next randomGen [_2]
split = reinsert split randomGen [_1, _2]
这种方法有问题。 reinsert
的类型声明是“非法多态类型”。我知道这是Haskell要处理的复杂类型。如果删除类型声明,则第一次使用a
镜头会将其转换为类Getting
,使第二次使用为ASetter
是非法的;列表理解中的b
也是如此。
是否可以解决此问题?或者,是否有更好的方法在RandomGen
上实现StateData
实例?
干杯,约翰
编辑:稍微简单一些,但不能解决核心问题:
instance RandomGen StateData where
next s = (s ^. randomGen ^. to next)
& _2 %~ (\ x -> s & randomGen .~ x)
split s = (s ^. randomGen ^. to split)
& _1 %~ (\ x -> s & randomGen .~ x)
& _2 %~ (\ x -> s & randomGen .~ x)
答案 0 :(得分:1)
作为一般规则,避免将Lens
/ Getter
/ Setter
等作为函数的参数,而将ALens
/ Getting
/ {{ 1}}。这些基本上是“专用于一种使用场景”的版本,不需要讨厌的Rank-N多态性等。Rank-N本身对于类型检查器来说只是棘手的,但是如果您也有这些类型的列表,它会完全崩溃(这是强制性的多态性,GHC从来没有正确支持它。)
因此,在这种情况下,它是ASetter
。唯一的小问题是,ALens
实际上想要.~
,它严格来说是比较特殊的,但是(在Haskell中)是不同的类型。 ASetter
也是如此。有两种解决方案:
#~
代表^.
。答案 1 :(得分:0)
您对reinsert
的定义可能存在的一个问题是,它将最终结果的结构与转换后的字段的结构联系在一起。
这个reinsert
的替代定义如何?
-- Pry apart a field from a value,
-- returning a pair of the value and a function to reconstruct the original value.
pry :: Lens' r x -> Iso' r (x -> r,x)
pry l = iso (\r -> (\x -> set l x r, view l r)) (uncurry ($))
-- Given
-- a lens into a field
-- a transformation of the field
-- a function that takes a reconstructor and the transformed field, and returns other thing
-- a starting value
-- return the other thing
reinsert :: Lens' a b -> (b -> b') -> ((b -> a) -> b' -> c) -> a -> c
reinsert l transform packer =
view $ pry l . alongside id (to transform) . to (uncurry packer)
它使用alongside
组合器。 (并非严格要求pry
,您只需在view
中使用set
和reinsert
。)
有了它,我们可以像这样定义RandomGen
实例:
instance RandomGen StateData where
next = reinsert randomGen next fmap
split = reinsert randomGen split (\f (s1,s2) -> (f s1, f s2))