Государственная монада Хаскелл - PullRequest
2 голосов
/ 30 июля 2010

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

import Control.Monad.State
type MyState = (Double,Double)
media s (a,n)= ((a*n+s)/(n+1),n+1)

getAverage:: Double ->State MyState  s1-> Double
getAverage s c=get >>= \s0 -> let (x,s1) =media s s0
            in put s1 >> return x

Я получил эту ошибку при компиляции в GHCI, и застрял тамВы помогаете мне понять, что не так, заранее спасибо

Ответы [ 2 ]

6 голосов
/ 30 июля 2010

Код, который вы предоставили, выдает эту ошибку:

Couldn't match expected type `Double'
       against inferred type `m Double'
In the expression:
      get >>= \ s0 -> let (x, s1) = ... in put s1 >> return x
In the definition of `getAverage':
    getAverage s c = get >>= \ s0 -> let ... in put s1 >> return x

Все это означает, что тип, являющийся результатом выражения ("выведенный"), не согласуется с сигнатурой типа ("ожидаемый").В этом случае getAverage работает в монаде State, так что это неверная сигнатура типа, поскольку она не может быть оценена как немонадный тип.

Ваш код имеет другие проблемы, кроме этого,однако, и не будет компилироваться даже после исправления этой конкретной проблемы.Сначала несколько стилистических проблем, чтобы сделать его более читабельным:

  • getAverage имеет неиспользуемый параметр, который предположительно является значением в монаде State, что в любом случае не имеет смысла.
  • Использование нотации do обычно яснее, чем использование (>>=) и лямбд, особенно для чего-то вроде State.
  • Отступ во второй строке сбивает с толку, поскольку inидет с let это внутри лямбда.

Делая эти изменения, мы имеем это:

getAverage s = do
    s0 <- get
    let (x, s1) = media s s0
    put s1 
    return x

... что облегчаетНайдите следующую ошибку: второй аргумент media - это 2-кортеж, а s1 - это просто одно число, но вы пытаетесь использовать оба для значения состояния.Вероятно, вы хотели установить состояние (x, s1), но вернуть только x.

getAverage s = do
    s0 <- get
    let (x,s1) = media s s0
    put (x,s1)
    return x

Это прекрасно компилируется, но все еще требует некоторой очистки:

  • media необходимо обновить все значение состояния, поэтому вместо get ting и put ting просто используйте функцию modify.
  • Возвращаемое значение - это первая часть значения состояния,так что просто fmap ing fst over get более просто.

Так что теперь у нас есть что-то вроде этого:

media :: Double -> MyState -> MyState
media s (a, n) = ((a * n + s) / (n + 1), n + 1)

getAverage:: Double -> State MyState Double
getAverage s = do
    modify (media s)
    fmap fst get

Мы также можем заметить, что getAverage делает две разные вещи и разделяет их на отдельные функции:

updateAverage:: Double -> State MyState ()
updateAverage s = modify (media s)

currentAverage :: State MyState Double
currentAverage = fmap fst get

getAverage:: Double -> State MyState Double
getAverage s = updateAverage s >> currentAverage

Редактировать : И так как я забыл о незначительной детализации фактического получения результатов изmonad, замена updateAverage на getAverage в функции getAverages Трэвиса Брауна позволит работать с моим кодом выше.

3 голосов
/ 30 июля 2010

Примечание: ответ camccann лучше моего, но мой использует немного другой подход и дает пример того, как оценивать монаду состояния, поэтому я оставляю его здесь для справки.


Мы можем начать пытаться выяснить проблему, удалив сигнатуру типа для getAverage и аргумент (c), который не отображается в функции:

getAverage s=get >>= \s0 -> let (x,s1) =media s s0
            in put s1 >> return x

Это все еще не компилируется, потому что мы пытаемся put что-то, что не имеет правильного типа: s1 это Double, а не MyState. Это легко исправить:

getAverage s=get >>= \s0 -> let s1@(x,_) =media s s0
            in put s1 >> return x

Мы также можем оставить шаблон let без изменений и просто сказать put (x,s1): вместо этого я делаю это так, чтобы наш s1 имел тот же тип, что и s0.

Это компилируется, так что теперь мы можем исправить сигнатуру типа. Если мы запрашиваем тип GHCi, он возвращает следующее:

getAverage :: (Fractional t, MonadState (t, t) m) => t -> m t

Double - это экземпляр Fractional, а State MyState - это экземпляр MonadState (Double, Double), поэтому мы можем использовать что-то очень похожее на ваш исходный тип для getAverage:

getAverage :: Double -> State MyState Double

Эта функция на самом деле не «получает» среднее значение: она обновляет его после добавления нового значения, поэтому давайте переименуем его соответствующим образом:

updateAverage :: Double -> State MyState Double
updateAverage s=get >>= \s0 -> let s1@(x,_) =media s s0
            in put s1 >> return x

Теперь мы можем определить функцию getAverages, которая принимает список Double с, пропускает их через updateAverage и возвращает список промежуточных средних на каждом шаге:

getAverages :: [Double] -> [Double]
getAverages ss = evalState (mapM updateAverage ss) (0, 0)

Это делает то, что мы ожидали:

*Main> getAverages [1..10]
[1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5]

Обратите внимание: чтобы сделать что-нибудь полезное с монадой State, вам всегда нужно будет использовать evalState (или тесно связанные runState и execState).

...