Сложная структура государственной монады

Я все еще новичок в Haskell, и я думаю, что сейчас я над головой. У меня есть код, который выглядит следующим образом.

data World = World {
  intStack :: [Int],
  boolStack :: [Bool]
} deriving Show

instance IntStack World where
   getIntStack = intStack
   putIntStack ints (World _ bools) = World ints bools

instance BoolStack World where
    getBoolStack = boolStack
    putBoolStack bools (World ints _) = World ints bools

class IntStack a where
    getIntStack :: a -> [Int]
    putIntStack :: [Int] -> a -> a

class BoolStack a where
    getBoolStack :: a -> [Bool]
    putBoolStack :: [Bool] -> a -> a

(<=>) :: (IntStack c, BoolStack c) => c -> c
(<=>) w = putIntStack xs . putBoolStack ((x == x'):bs) $ w
    where (x:x':xs) = getIntStack w
          bs = getBoolStack w

(<+>) :: (IntStack c) => c -> c
(<+>) w = putIntStack ((x+x'):xs) w
    where (x:x':xs) = getIntStack w

Мое внимание (пока игнорируя случаи ошибок в функциях) заключается в возможности связывать вместе такие функции, как (‹=>) и (‹+>), предполагая, что базовый тип данных реализует требуемые интерфейсы функции.

Я чувствую, что могу многое исправить с помощью монады состояния, но я не уверен, как ее структурировать, чтобы разрешить изменения любого типа данных, который реализует IntStack, BoolStack и т. д.

Я знаю, что это ужасно расплывчатое описание, но я чувствую, что приведенный выше код, вероятно, абсолютно неправильный способ сделать это.

Спасибо за любой отзыв!


person Charles Durham    schedule 10.10.2011    source источник


Ответы (2)


class IntStack a where
    getIntStack :: a -> [Int]
    putIntStack :: [Int] -> a -> a

class BoolStack a where
    getBoolStack :: a -> [Bool]
    putBoolStack :: [Bool] -> a -> a

Поздравляем, вы только что изобрели линзы! Абстрагируйте типы [Int] и [Bool] и используйте data вместо class, и вы получите что-то вроде

data Lens a b = Lens
    { get :: a -> b
    , put :: b -> a -> a
    }

...который реализован в полудюжине пакетов на Hackage. Большинство предлагают как минимум:

  • возможность получать проекционные линзы, такие как ваши getIntStack/putIntStack и getBoolStack/putBoolStack, непосредственно из объявления данных
  • горизонтальную композицию (сначала запускаем одну линзу, затем вторую линзу — например, сначала выбираем World из какой-то более крупной структуры, затем выбираем intStack из World) и вертикальную композицию (запускаем две линзы параллельно, каждая на одной стороне пара)
  • какой-нибудь интерфейс с State и StateT (например, что-то типа Lens a b -> State b r -> State a r), который позволит вам писать вычисления на [Bool] или [Int] и запускать их, как если бы они были вычислениями на World

Итак, проверьте взлом! Есть семейство data-lens, которое включает ядро, возможность извлечения и интерфейс с отслеживанием состояния ; пакет lens; и пакет pointless-lenses. Есть, наверное, и те, что я забыл.

person Daniel Wagner    schedule 10.10.2011
comment
Ура, я также (повторно) изобрел линзы! Я сейчас вытаскиваю пакеты из hackage. +1 - person pat; 10.10.2011
comment
Большое спасибо за это, я совершенно сногсшибательно, насколько они полезны. - person Charles Durham; 18.10.2011

Вы можете реализовать операции push и pop в монаде состояния и использовать их для реализации своих функций:

popInt :: IntStack c => State c Int
popInt = do
  (x:xs) <- getIntStack <$> get
  modify $ putIntStack xs
  return x

pushInt :: IntStack c => Int -> State c ()
pushInt x = do
  xs <- getIntStack <$> get
  modify $ putIntStack (x:xs)

(<+>) :: IntStack c => State c ()
(<+>) = do 
  x <- popInt
  x' <- popInt
  pushInt (x + x')
person Sjoerd Visscher    schedule 10.10.2011