государственная монада haskell

Я хочу написать функцию для вычисления среднего значения с использованием 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, и я застрял там, не могли бы вы помочь мне понять, что не так, заранее спасибо


person arpho    schedule 30.07.2010    source источник
comment
Вы должны вставить то, что ошибка была.   -  person Josh Lee    schedule 30.07.2010
comment
Что вы вводите в свою командную строку, чтобы запустить эту функцию getAverage?   -  person Sam Heather    schedule 13.05.2014


Ответы (2)


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

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 представляет собой двойку, а s1 — всего лишь одно число, но вы пытаетесь использовать оба значения для значения состояния. Вероятно, вы хотели установить состояние (x, s1), но вернуть только x.

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

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

  • media необходимо обновить все значение состояния, поэтому вместо getting и putting просто используйте функцию modify.
  • Возвращаемое значение — это первая часть значения состояния, поэтому просто fmapобъединить fst поверх 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

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

person C. A. McCann    schedule 30.07.2010
comment
@camccann - как мне запустить это из командной строки? Я загрузил его в ghci, но не уверен, каким должен быть вызов для getAverage. - person Sam Heather; 13.05.2014
comment
@SamHeather: вам нужно использовать runState или аналогичную функцию, например. runState (getAverage 1) (2, 3). См. документацию по монаде состояния здесь: hackage.haskell.org/package/mtl-2.2.0.1/docs/ Если у вас есть вопросы помимо этого, вам, вероятно, больше повезет, опубликовав их как новый вопрос. - person C. A. McCann; 13.05.2014

Примечание: ответ 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).

person Travis Brown    schedule 30.07.2010
comment
Небольшое замечание, которое, как я думал, не поможет задавшему вопрос, но которое может вам понравиться: обратите внимание, что нет никакого управления потоком, зависящего от значений состояния? Это явный признак того, что этой монаде не нужна, и поэтому ваша версия может быть переписана как updateAverage s = modify (media s) *> (fst <$> get). Точно так же mapM можно обобщить как traverse, что делает то же самое для любого Applicative до любого Traversable. - person C. A. McCann; 30.07.2010