在 Haskell 中使用镜头更新状态结构中的 RandomGen
Using lenses to update RandomGen inside state struct in Haskell
我在较大的状态结构中使用 StdGen
,并希望在状态结构上实现 RandomGen
class。使用镜头,我想出了以下实现:
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
的类型声明是一个 "Illegal polymorphic type"。我将此理解为 Haskell 处理起来过于复杂的类型。如果我删除类型声明,第一次使用 a
镜头会将其变成 class Getting
,使第二次使用 ASetter
非法;列表理解中的 b
也会发生同样的情况。
有办法解决这个问题吗?或者,是否有更好的方法在 StateData
上实现 RandomGen
实例?
干杯,约翰
编辑:简单一点,但没有解决核心问题:
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)
作为一般规则,避免使用 Lens
/ Getter
/ Setter
等作为函数的参数,使用 ALens
/ Getting
/ ASetter
代替。这些基本上是“专用于一种使用场景”的版本,不需要讨厌的 Rank-N 多态性等。Rank-N 本身对于类型检查器来说只是棘手的,但如果你也有这些类型 在列表中,它完全崩溃了(这将是非谓语多态性,GHC 从未正确支持它)。
所以在这种情况下,它是 ALens
。唯一的小问题是,.~
实际上想要 ASetter
,这是一个更特殊但(在 Haskell 中)不同的类型。 ^.
也是如此。有两种解决方案:
- “Clone” the lens,再次在函数中获取多态版本。
- 使用“lensy”getter 和 setter 运算符,即
#~
代表 .~
。
您定义 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
不是严格需要的,您可以在 reinsert
中简单地 view
和 set
。)
有了它,我们可以这样定义 RandomGen
实例:
instance RandomGen StateData where
next = reinsert randomGen next fmap
split = reinsert randomGen split (\f (s1,s2) -> (f s1, f s2))
我在较大的状态结构中使用 StdGen
,并希望在状态结构上实现 RandomGen
class。使用镜头,我想出了以下实现:
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
的类型声明是一个 "Illegal polymorphic type"。我将此理解为 Haskell 处理起来过于复杂的类型。如果我删除类型声明,第一次使用 a
镜头会将其变成 class Getting
,使第二次使用 ASetter
非法;列表理解中的 b
也会发生同样的情况。
有办法解决这个问题吗?或者,是否有更好的方法在 StateData
上实现 RandomGen
实例?
干杯,约翰
编辑:简单一点,但没有解决核心问题:
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)
作为一般规则,避免使用 Lens
/ Getter
/ Setter
等作为函数的参数,使用 ALens
/ Getting
/ ASetter
代替。这些基本上是“专用于一种使用场景”的版本,不需要讨厌的 Rank-N 多态性等。Rank-N 本身对于类型检查器来说只是棘手的,但如果你也有这些类型 在列表中,它完全崩溃了(这将是非谓语多态性,GHC 从未正确支持它)。
所以在这种情况下,它是 ALens
。唯一的小问题是,.~
实际上想要 ASetter
,这是一个更特殊但(在 Haskell 中)不同的类型。 ^.
也是如此。有两种解决方案:
- “Clone” the lens,再次在函数中获取多态版本。
- 使用“lensy”getter 和 setter 运算符,即
#~
代表.~
。
您定义 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
不是严格需要的,您可以在 reinsert
中简单地 view
和 set
。)
有了它,我们可以这样定义 RandomGen
实例:
instance RandomGen StateData where
next = reinsert randomGen next fmap
split = reinsert randomGen split (\f (s1,s2) -> (f s1, f s2))