Использование линз для обновления RandomGen внутри структуры состояния в Haskell - PullRequest
0 голосов
/ 13 декабря 2018

Я использую 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 является «Недопустимым полиморфным типом».Я понимаю это как тип сложного для Хаскелла.Если я удаляю объявление типа, первое использование объектива a превращает его в класс Getting, что делает второе использование как ASetter недопустимым;то же самое происходит с b в понимании списка.

Есть ли способ исправить это?Или, в качестве альтернативы, есть ли лучший способ реализовать экземпляр RandomGen на StateData?

Cheers, Johan

Редактировать: немного проще, но не решить основную проблему:

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 голосов
/ 16 декабря 2018

Одна из возможных проблем с вашим определением 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))
0 голосов
/ 13 декабря 2018

Как правило, избегайте использования Lens / Getter / Setter и т. Д. В качестве аргументов функций, вместо этого принимайте ALens / Getting / ASetter.Это в основном версии «специализированного сценария для одного использования», которые не требуют неприятного полиморфизма Ранка-N и т. Д. Сам по себе Ранк-N просто сложен для проверки типов, но если у вас также есть эти типы всписок , он полностью разрушается (это был бы непредсказуемый полиморфизм, который GHC никогда не поддерживал должным образом).

Так что в данном случае это ALens.Единственная небольшая проблема в том, что .~ на самом деле хочет ASetter, что является строго более особенным, но (в Хаскеле) отличным типом.Аналогично для ^..Существует два решения:

  • «Клонировать» объектив , чтобы снова получить в полиморфной версии функцию.
  • Использовать «линзовый» геттер и сеттероператоры, то есть #~ для .~.
...