Состояние моей программы состоит из трех значений: a
, b
и c
, типов a
, b
и c
. Различные функции требуют доступа к различным значениям. Я хочу писать функции с помощью монады State
, чтобы каждая функция могла получить доступ только к частям состояния, к которым он должен получить доступ.
У меня есть четыре функции следующих типов:
f :: State (A, B, C) x
g :: y -> State (A, B) x
h :: y -> State (B, C) x
i :: y -> State (A, C) x
Вот как я вызываю g
внутри f
:
f = do
-- some stuff
-- y is bound to an expression somewhere in here
-- more stuff
x <- g' y
-- even more stuff
where g' y = do
(a, b, c) <- get
let (x, (a', b')) = runState (g y) (a, b)
put (a', b', c)
return x
Эта функция g'
является уродливой частью шаблона, которая ничего не делает, кроме как преодоление зазора между типами (A, B, C)
и (A, B)
. Это в основном версия g
, которая работает в состоянии с тремя кортежами, но оставляет третий элемент кортежа без изменений. Я ищу способ написать f
без этого шаблона. Может быть, что-то вроде этого:
f = do
-- stuff
x <- convert (0,1,2) (g y)
-- more stuff
Где convert (0,1,2)
преобразует вычисление типа State (a, b) x
в тип State (a, b, c) x
. Аналогично, для всех типов a
, b
, c
, d
:
-
convert (2,0,1)
преобразуетState (c,a) x
вState (a,b,c) x
-
convert (0,1)
преобразуетState b x
вState (a,b) x
-
convert (0,2,1,0)
преобразуетState (c,b) x
вState (a,b,c,d) x
Мои вопросы:
- Есть ли лучшее решение, чем установка значений состояния в кортежах? Я думал об использовании стека трансформатора монады. Тем не менее, я думаю, что работает только если для любых двух функций
f
иg
либоf
⊆g
, либоg
⊆f
, гдеf
- множество значений состояния, необходимых дляf
иg
- это набор значений состояния, необходимых дляg
. Неужели я ошибаюсь? (Обратите внимание, что мой пример не удовлетворяет этому свойству. Например,g
={a, b}
иH
={b, c}
. Ни одно из подмножеств другого.) - Если нет лучшего способа, чем кортежи, то есть ли хороший способ избежать описанного выше шаблона? Я даже готов написать файл с кучей функций шаблонов (см. Ниже), если файл может быть автоматически сгенерирован один раз, а затем забыт. Есть ли способ лучше? (Я читал об объективах, но их сложность, уродливый синтаксис, огромный набор ненужных функций и кажущаяся зависимость от Template Haskell отключаются. Является ли это неправильным пониманием? Могут ли линзы решить мою проблему таким образом, чтобы избежать этих проблем?)
(Функции, о которых я говорил, выглядят примерно так.)
convert_0_1_2 :: State (a, b) x -> State (a, b, c) x
convert_0_1_2 f = do
(a, b, c) <- get
let (x, (a', b')) = runState f (a, b)
put (a', b', c)
return x
convert_0_2_1_0 :: State (c, b) x -> State (a, b, c, d) x
convert_0_2_1_0 f = do
(a, b, c, d) <- get
let (x, (b', c')) = runState f (b, c)
put (a, b', c', d)
return x