2012-10-17 2 views
0

Можно создать дубликат:
Creating unique labels in Haskellидентификаторов из Государственной монады в Haskell

У меня есть тип данных Person и некоторые входные данные, из которых я буду создавать Лицо.

Я бы хотел, чтобы у каждого человека был свой идентификатор (скажем, целые числа [0 ..]). Я мог бы сделать это с рекурсией, но, поскольку я делаю это в Haskell, я хотел бы понять монады. Думаю, что Государственная Монада, вероятно, лучше всего подходит для этой работы?

Дело в том, что я не очень понимаю много вещей: когда я внутри монады (какие функции могут использовать внутри), как я их откинуть, как мне сделать функцию «тика» , и т. д.

Так что я в настоящее время застрял в этом: функция галочки, вероятно, работает, но я не уверен, как ее использовать; и как последовательно получать свою ценность для строительства Лиц.

import Control.Monad.State 

data Person = Person { 
    id :: Int, 
    name :: String 
} deriving Show 

type MyState = Int 
startState = 0 

tick :: State MyState Int 
tick = do 
    n <- get 
    put (n+1) 
    return n 

names = ["Adam","Barney","Charlie"] 

-- ??? -> persons = [Person 0 "Adam", Person 1 "Barney", Person 2 "Charlie"] 

main = do 
    print $ evalState tick startState 
    -- ??? 

EDIT: это было бы как-то проще с Data.Unique или Data.Unique.Id? Как он будет использоваться в моей ситуации?

+0

Вы должны рассмотреть соответствие тип данных более правильно с вашей проблемой, как для экземпляр карты. – Sarah

+0

Возможно, вы захотите изучить [этот недавний вопрос о узлах дерева меток] (http://stackoverflow.com/questions/12658443/how-to-decorate-a-tree-in-haskell/12658639). –

ответ

5

Ну, я думаю, что лучший способ объяснить это просто написать код.

Прежде всего, вы хотели бы скрыть внутреннюю работу монады, в которой вы сейчас работаете. Мы сделаем это с псевдонимом типа, но есть более мощные способы, см. Эту главу от Real World Haskell.

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 тип вычислений, или процесс, который инкапсулирует логику для работы с людьми и PersonManagement Person - это вычисление, из которого мы получаем объект человека. Это очень приятно, но как мы на самом деле получаем людей, которых мы только что создали, и что-то с ними делать, например, печатать их данные на консоли. Ну, нам нужен метод «run», который запускает наш процесс и дает нам результат.

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 является полноценным monad, вы также можете использовать общие функции от 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 - отличное начало, и поэтому Learn you a Haskell.

Вы также должны изучить некоторые библиотеки, в которых используются монады, чтобы увидеть, как они созданы и как люди используют их. Один замечательный пример - синтаксические анализаторы, и parsec - отличное место для начала.

Кроме того, этот paper П. Вадлер предоставляет несколько очень приятных примеров, и, конечно же, есть еще много ресурсов, которые готовы быть обнаружены.

+0

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

+0

Что делать, если вы хотите постоянно менять ID во время выполнения программы? Я чувствую, что мы «застряли» в государственной монаде. – MFlamer

1

Лучше делать с mapAccumL как

getPersons = snd . mapAccumL f 0 
    where 
     f n name = (n+1,Person n name) 

В любом случае я изменил свою программу, чтобы сделать это делать с государственной монады

import Control.Monad.State 

data Person = Person { 
    id :: Int, 
    name :: String 
} deriving Show 

type MyState = Int 
startState = 0 

tick :: State MyState Int 
tick = do 
    n <- get 
    put (n+1) 
    return n 

getPerson :: String -> State MyState Person 
getPerson ps = do 
    n <- tick 
    return (Person n ps) 


names = ["Adam","Barney","Charlie"] 

getPersonsExample :: State MyState [Person] 
getPersonsExample = do 
    a <- getPerson "Adam" 
    b <- getPerson "Barney" 
    c <- getPerson "Charlie" 
    return ([a,b,c]) 

main1 = do 
    print $ evalState (sequence $ map getPerson names) startState 

main2 = do 
    print $ evalState getPersonsExample startState 
+0

Уверен, что 'fold' лучше в этом примере, где у нас просто есть список имен, но для реального применения этого решение' State', безусловно, гораздо более управляемо. Но ваше решение на самом деле не использует государственную монаду, она просто держит упаковку и разворачивает ее новый тип! – leftaroundabout

+0

@leftaroundabout См. Измененное решение – Satvik

+0

Спасибо! Итак, если я правильно понимаю, если я в функции типа ... -> State MyState ..., то я могу использовать все другие функции этого типа? (например, внутри getPerson я могу использовать tick?) –

1

Монады в do синтаксической работы во многих отношениях весьма «так же, как вы» d expect ", рассматривая все это так, как если бы это был императивный язык.

Итак, что мы хотим сделать здесь, процедурно? Итерации над этими именами, не так ли? Как насчет

forM names 

с forM из Control.Monad. Это очень похоже на цикл for, как вы его знаете. Хорошо, сначала мы должны связать каждое имя переменной

forM names $ \thisName -> do 

Что мы хотели бы сделать? Нам нужен идентификатор, tick сгенерирует его для нас

newId <- tick 

и объединить его с именем человека. Вот и все!

return $ Person newId thisName 

все это будет выглядеть следующим образом:

(persons, lastId) = (`runState` startState) $ do 
    forM names $ \thisName -> do 
     newId <- tick 
     return $ Person newId thisName 

который works as expected, или же, если Ideone был установлен пакет MTL ...

0

Настоящая трудность здесь заключается в определении и решении проблемы, по которой идентификаторы должны быть уникальными. Используя монумент State и экземпляр Succ (как в моем примере ниже), можно легко массировать, чтобы гарантировать уникальность в рамках одного вычисления монады State. С небольшим вниманием (захват конечного состояния после runState и обеспечение его использования в качестве начального состояния в следующем runState) вы можете гарантировать уникальность множества вычислений State, но, вероятно, лучше просто составить эти два вычисления в более крупный один.

Data.Unique и Data.Unique.Id может показаться проще, но есть две проблемы, которые нужно иметь в виду:

  1. Ваш код будет привязан к IO монады.
  2. Модули Unique не имеют явных сведений о области, в которой сгенерированные идентификаторы уникальны. Сохраняет ли ваша программа, может ли один и тот же идентификатор назначаться двум различным лицам в разных программах? Влияет ли ваша программа на возможность «восстановить» назначения Person-to-ID из предыдущих исполнений?

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

Во всяком случае, вот мой взять на свой код (полностью проверялось, может даже не компилируется, но вы должны получить идею):

import Control.Monad (mapM) -- Study the Control.Monad module carefully... 

-- Your "tick" action can be made more generic by using `Enum` instead of numbers 
postIncrement :: Enum s => State s s 
postIncrement = do r <- get 
        put (succ r) 
        return r 

-- Action to make a labeled Person from a name. 
makePersonM :: String -> State Int Person 
makePersonM name = do label <- postIncrement 
         return $ Person label name 

-- The glue you're missing is mapM 
whatYouWant = evalState (mapM makePersonM names) 0 
Смежные вопросы