Haskell - Как мне запустить список монад состояния?

Я новичок в Haskell, и у меня возникли проблемы с монадой State.

Я создал следующие типы. Stat a для него созданы моноид, функтор, аппликатив и экземпляр монады.

«Главный» тип в моей программе — это существа, и у него много аргументов:

data Creature = Creature {
    strength  :: Stat Integer,
    dexterity :: Stat Integer,
    ...
}

data Stat a = Stat {
    modifiers :: [StatModifier],
    stat      :: a
}

data StatModifier = StatModifier {
    modifierType :: ModifierType,
    value        :: Integer
}

data ModifierType = 
  Enhancement
  | Morale
  | ...

С существом может случиться многое. Я решил представить эти вещи с помощью монады состояния:

anyPossibleChange :: State Creature Creature

Это может быть повреждение, наносимое существу, увеличение силы существа, в общем, что угодно. Возможность чего угодно заставила меня подумать, что монада State была хорошим выбором. Я принимаю существо в его исходном состоянии, выполняю некоторую модификацию и возвращаю исходное состояние и новое состояние в виде кортежа.

Исходное состояние может быть:

Creature {
    strength = Stat [] 10,
    dexterity = Stat [] 10
}

Конечное состояние может быть:

Creature {
    strength = Stat [StatModifier Enhancement 2] 10,
    dexterity = Stat [StatModifier Enhancement 4, StatModifier Morale 2] 10
}

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

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

applyChanges :: Creature -> [State Creature Creature] ->  Creature

Я чувствую, что должен быть в состоянии сделать это со сгибом, возможно, FoldM, но мой мозг зацикливается на типах.

Какой должна быть хорошая реализация?


person Nick Acosta    schedule 26.05.2017    source источник
comment
Что такое начальное состояние? Что должно произойти, когда список пуст? Является ли ваш Creature Monoid?   -  person Bergi    schedule 26.05.2017
comment
Я отредактировал свой вопрос и добавил пример начального и конечного состояния, а также еще несколько типов для пояснения. Это упрощенная версия моего существа, а настоящее не моноид. Если список пуст, думаю, я ожидаю вернуть исходное существо.   -  person Nick Acosta    schedule 26.05.2017
comment
Проблема, которую я увидел, заключается в том, что предложенная вами функция applyChanges не принимает существо в его исходном состоянии.   -  person Bergi    schedule 26.05.2017
comment
Отредактировано. Я открыт для любых изменений вышеизложенного. Моя общая цель — провести существо через каждое из действий состояния в этом списке.   -  person Nick Acosta    schedule 26.05.2017
comment
Почему они вообще находятся в состоянии, если состояние всегда должно быть исходным вводом? Кажется, что каждое изменение должно быть просто Creature -> Creature, и тогда вашу функцию applyChanges :: [Creature -> Creature] -> Creature -> Creature очень легко реализовать: applyChanges = foldr (.) id   -  person amalloy    schedule 26.05.2017
comment
Ах, я верю, что ты прав. Я поторопился с ненужной сложностью здесь и, вероятно, неправильно использовал монаду состояния. Если хотите, добавьте ответ, и я отмечу его как таковой.   -  person Nick Acosta    schedule 26.05.2017
comment
Использование State может быть в некотором смысле преждевременным, но я бы все равно это сделал, потому что сразу могу придумать причины, по которым вы захотите. 1. Если вы хотите получить не только конечное состояние, но и список промежуточных состояний. Вы можете сделать это с помощью scanl, но если вы начнете с State, это действительно естественно. 2. Вы хотите добавить некоторые другие эффекты. Если вы начинаете с State, вам нужно всего лишь переключиться на StateT для соответствующего базового типа действия.   -  person dfeuer    schedule 26.05.2017
comment
@dfeuer В этом случае вам понадобится State Creature a, где значение состояния — это то, что вы действительно хотите получить в результате (через execState), а не монадическое значение, которое вы получили бы от evalState, верно? Это, безусловно, имеет для меня некоторый смысл, но State Creature Creature кажется слишком конкретным, требуя, чтобы каждое преобразование заканчивалось на get, чтобы перевести состояние Creature в монадическое значение. State Creature () выглядит более разумным в качестве цели для составления списка преобразований с отслеживанием состояния.   -  person amalloy    schedule 26.05.2017
comment
@amalloy, да, State Creature (), наверное, правильное место для начала.   -  person dfeuer    schedule 26.05.2017
comment
@defeuer Я тоже об этом думал и держу это в голове. Это то, что я хочу сделать в конечном итоге, но пока это работает. Когда это время придет, я думаю, что преобразование из функции в состояние будет легким изменением, за исключением части сгиба, в которой я все еще немного туманен.   -  person Nick Acosta    schedule 26.05.2017
comment
@NickAcosta, складки, на которые нужно смотреть здесь (в Data.Foldable), это traverse_ и (реже) sequenceA_. Вскоре вас заинтересуют обходы traverse и sequenceA.   -  person dfeuer    schedule 26.05.2017
comment
Спасибо, я проверю их.   -  person Nick Acosta    schedule 27.05.2017


Ответы (1)


State Creature Creature — неправильный тип для такого рода вычислений. Как вы видели, вам это может сойти с рук, но это излишне усложняет ситуацию, потому что на самом деле вы вообще не заботитесь о переменной состояния! Вы просто используете его для хранения исходного ввода функции... но затем выбрасываете его в applyChanges.

Тип, с которым было бы намного проще работать, — это Creature -> Creature, а затем, если у вас есть список таких функций для применения, вы просто хотите скомпоновать их все, что вы можете сделать с помощью fold:

applyChanges :: [Creature -> Creature] -> Creature -> Creature
applyChanges = foldr (.) id
person amalloy    schedule 26.05.2017
comment
Я новичок в haskell и с трудом представляю, как это будет работать на практике. Если бы у нас был StatModifer, добавляющий 2 к характеристике, и функция, применяющая этот модификатор ко всем характеристикам, как бы выглядела реализация? - person Dan Ambrogio; 26.05.2017
comment
@DanAmbrogio: Если я понимаю ваш вопрос, у вас будет addMod :: StatModifier -> Stat a -> Stat a; addMod m (Stat ms a) = Stat (m : ms) a, чтобы добавить модификатор к статистике, и помощники, такие как modAll :: StatModifier -> Creature -> Creature; modAll m c = c { strength = addMod m (strength c), … }, например, для изменения всей статистики. Затем вы должны создать Creature -> Creature функции, подобные enhanceAll = modAll (StatModifier Enhancement 2), и составить их или использовать как State действия. foldr (.) id [f, g, h] x аналогичен f (g (h (id x))): он применяет каждую функцию справа налево. - person Jon Purdy; 27.05.2017
comment
Возможно, стоит задать собственный вопрос SO. Это, безусловно, было бы более читаемым в ответе. - person Nick Acosta; 27.05.2017