使用镜头更新Haskell中的RandomGen内部状态结构

时间:2018-12-13 12:53:20

标签: haskell lens

我在较大的状态结构中使用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)

2 个答案:

答案 0 :(得分:1)

作为一般规则,避免将Lens / Getter / Setter等作为函数的参数,而将ALens / Getting / {{ 1}}。这些基本上是“专用于一种使用场景”的版本,不需要讨厌的Rank-N多态性等。Rank-N本身对于类型检查器来说只是棘手的,但是如果您也有这些类型的列表,它会完全崩溃(这是强制性的多态性,GHC从来没有正确支持它。)

因此,在这种情况下,它是ASetter。唯一的小问题是,ALens实际上想要.~,它严格来说是比较特殊的,但是(在Haskell中)是不同的类型。 ASetter也是如此。有两种解决方案:

  • “Clone” the lens,以再次使用该功能的多态版本。
  • 使用“笨拙的” getter和setter运算符,即#~代表^.

答案 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中使用setreinsert。)

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

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