在 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 中简单地 viewset。)

有了它,我们可以这样定义 RandomGen 实例:

instance RandomGen StateData where
    next  = reinsert randomGen next fmap
    split = reinsert randomGen split (\f (s1,s2) -> (f s1, f s2))