Ну, я думаю, что лучший способ объяснить это просто написать код.
Прежде всего, вы хотели бы также скрыть внутреннюю работу монады, в которой вы сейчас работаете. Мы сделаем это с помощью псевдонима типа, но есть и более мощные способы, см. эту главу из Хаскель реального мира.
type PersonManagement = State Int
Причина этого в том, что вы добавите больше вещей в PersonManagement позже, и хорошей практикой является использование абстракции черного ящика.
Вместе с определением PersonManagement вы должны раскрыть примитивные операции, определяющие эту монаду. В вашем случае у нас пока есть только функция галочки, которая выглядит почти так же, но с более четкой подписью и более наводящим на размышления названием.
generatePersonId :: PersonManagement Int
generatePersonId = do
n <- get
put (n+1)
return n
Теперь все вышеперечисленное должно находиться в отдельном модуле. Кроме того, мы можем определить более сложные операции, такие как создание нового человека:
createPerson :: String -> PersonManagement Person
createPerson name = do
id <- generatePersonId
return $ Person id name
К настоящему времени вы, вероятно, поняли, что PersonManagement — это тип вычислений или процесс, который инкапсулирует логику для работы с Persons, а PersonManagement Person
— это вычисление, из которого мы получаем объект person. Это очень мило, но как нам на самом деле получить людей, которых мы только что создали, и что-то с ними сделать, например, распечатать их данные на консоли. Что ж, нам нужен метод «запустить», который запускает наш процесс и выдает нам результат.
runPersonManagement :: PersonManagement a -> a
runPersonManagement m = evalState m startState
runPersonManagement запускает монаду и получает окончательный результат, выполняя все побочные эффекты в фоновом режиме (в вашем случае отмечая состояние Int). При этом используется evalState из монады состояния, и он также должен находиться в модуле, описанном выше, поскольку он знает о внутренней работе монады. Я предположил, что вы всегда хотите начинать идентификатор человека с фиксированного значения, определяемого startState.
Так, например, если мы хотим создать двух человек и вывести их на консоль, программа будет выглядеть примерно так:
work :: PersonManagement (Person, Person)
work = do
john <- createPerson "John"
steve <- createPerson "Steve"
return (john, steve)
main = do
let (john, steve) = runPersonManagement work
putStrLn $ show john
putStrLn $ show steve
Вывод:
Person {id = 0, name = "John"}
Person {id = 1, name = "Steve"}
Поскольку PersonManagement является полноценной монадой, вы также можете использовать универсальные функции из Control.Monad например. Допустим, вы хотите создать список лиц из списка имен. Ну, вот только функцию карты подняли в область монад - она называется mapM.
createFromNames :: [String] -> PersonManagement [Person]
createFromNames names = mapM createPerson names
Использование:
runPersonManagement $ createFromNames ["Alice", "Bob", "Mike"] =>
[
Person {id = 0, name = "Alice"},
Person {id = 1, name = "Bob"},
Person {id = 2, name = "Mike"}
]
И примеры можно продолжать.
Чтобы ответить на один из ваших вопросов - вы работаете в монаде PersonManagement только тогда, когда вам нужны услуги, предоставляемые этой монадой - в этом случае функция generatePersonId или вам нужны функции, которые, в свою очередь, требуют примитивов монады, таких как work
, которая нуждается в функции createPerson
, которая в свою очередь, должен выполняться внутри монады PersonManagement, потому что ей нужен самоувеличивающийся счетчик. Если у вас есть, например, функция, которая проверяет, имеют ли два человека одинаковые данные, вам не нужно работать внутри монады PersonManagement, и это должна быть обычная, чистая функция типа Person -> Person -> Bool
.
Чтобы действительно понять, как работать с монадами, вам просто нужно пройти через множество примеров. Real World Haskell — отличное начало, как и Изучите Haskell.
Вам также следует заглянуть в некоторые библиотеки, использующие монады, чтобы увидеть, как они создаются и как люди их используют. Отличным примером являются синтаксические анализаторы, и parsec — отличное место для начала.
Кроме того, этот статья П. Вадлера содержит очень хорошие примеры и, конечно же, есть много других ресурсов, которые готовы к открытию.
person
Marius Danila
schedule
17.10.2012