Что делает команда put в типичной функции, использующей монаду состояния?

Это пример из https://wiki.haskell.org/All_About_Monads Это пример использования State монады для передачи значения StdGen через последовательность команд генерации случайных чисел. Если я понимаю, что последний return делает правильно, он должен просто создать новую монаду с x в качестве значения. Но что тогда на самом деле делает put g'? Почему бы на самом деле g' не потеряться?

getAny :: (Random a) => State StdGen a
getAny = do g <- get
            (x,g') <- return $ random g
            put g'
            return x

person Gherman    schedule 19.06.2016    source источник
comment
Это вычисление состояния может быть составлено с другими, поэтому оно не «потеряно».   -  person pdexter    schedule 19.06.2016
comment
@pdexter, я имею в виду заблудиться между put g' и return x. return x следует создать новый экземпляр без g', верно? Тогда где g'? Может я неправильно понимаю, что делает return в do-нотации? Я думал, что это просто реализация return стратегии монады.   -  person Gherman    schedule 19.06.2016
comment
State StdGen a представляет вычисление со значением состояния типа StdGen. Просто представьте, что существует изменяемая переменная императивного стиля типа StdGen, которую можно читать с помощью действия get и записывать с помощью действия put x (x - новое значение). (x,g') <- return $ random g - это ИМХО антипаттерн, который лучше всего написано let (x,g') = random g.   -  person chi    schedule 19.06.2016


Ответы (2)


Я думаю, тебя смущает

(x, g') <- return $ random g

Это действительно создает новое монадическое действие State StdGen (a, StdGen), которое выполняется для извлечения его результата (a, StdGen).

Путаница возникает не без оснований, потому что код фактически эквивалентен

let (x, g') = random g

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

Во всяком случае, техническая часть: фрагмент (x, g') <- return $ random g означает

(x, g') <- State (\g'' -> (random g, g''))

где мы можем видеть, что монадическое действие принимает текущее состояние g'' (которое имеет то же значение, что и g), а затем не изменяет его (часть (..., g'')), возвращая вместе с ним сгенерированное значение random g (часть (random g, ...)).

Это немного глупо, поскольку нам даже не нужно читать g'', поскольку мы используем random g!

Итак, мы используем

do g       <- State (\g'' -> (g'', g''))
   (x, g') <- State (\g'' -> (random g, g''))
   ...

когда мы могли бы вместо этого использовать

do (x, g') <- State (\g'' -> (random g'', g''))
   ...

который вызывается в библиотеке

do (x, g') <- gets random
   ...

Ладно, похоже, что путаница в do put g' ; return x. Это сокращается в связной нотации следующим образом

{ definitions }
put g'   = State $ \s -> ((), g')
return x = State $ \s -> (x , s )

do put g ; return x 
= { definitions, desugaring }
   (State $ \s -> ((), g'))
   >>= 
   (\_ -> State $ \s -> (x , s ))
= { definition of >>= }
   State $ \s -> let (v,s') = (\s -> ((), g')) s 
                 in runState ((\_ -> State $ \s -> (x , s )) v) s'
= { beta-reduction (application) }
   State $ \s -> let (v,s') = ((), g')
                 in runState (State $ \s -> (x , s )) s'
= { beta-reduction (let) }
   State $ \s -> runState (State $ \s -> (x , s )) g'
= { runState (State y) = y }
   State $ \s -> (\s -> (x , s )) g'
= { beta-reduction }
   State $ \s -> (x , g')

Таким образом, эффект do put g' ; return x заключается в изменении состояния на g' (перезапись предыдущего s) и выдаче x в качестве окончательного значения вычисления (вместе с g').

person chi    schedule 19.06.2016
comment
Прошу прощения за недостаточную ясность. Возвращаясь к последней строке. Я понимаю, что делают первые две строчки. Но спасибо и за это улучшение. Последний return x должен создать новую монаду State без g', верно? - person Gherman; 19.06.2016
comment
@German Обратите внимание, что State StdGen a не имеет состояния внутри, но является модификатором состояния, примерно функцией \g -> (someValueDependingOn g, newG g). Таким образом, return x действительно не включает g' , но будет составлен из нотации do с put и остальными выше, так что весь блок do будет чем-то вроде \g -> (x, g'), где x,g' вычисляются в терминах g . Итак, g' не потеряно: последний возврат не отменяет эффекты предыдущих действий. - person chi; 19.06.2016
comment
Итак, return в нотации do на самом деле не является return стратегией монад, а скорее вызывает стратегию связывания монад? Я думал, что это то же самое. - person Gherman; 19.06.2016
comment
Возвращение - это действительно возвращение монады. Но do a;b;c;d использует привязку для подключения a,b,c,d. Если d возвращается, a,b,c все еще имеет значение. Обратите внимание, что согласно законам монад, do x <- action; return x эквивалентно action - возврат не имеет никакого эффекта, но действие по-прежнему актуально. - person chi; 19.06.2016
comment
Значит, это обе стратегии. Спасибо! Если вы сделаете эти два комментария в ответ, я могу с ним согласиться. - person Gherman; 19.06.2016

Предположим, что наше состояние хранится в файле. Вот что делает getAny, как это выражено на языке, подобном Javascript / Python:

function getAny() {
  var g = readStateFromFile("some-path")  // the get call

  var xg  = random(g)    // returns an array with two elements
  var x = xg[0]
  var newg = xg[1]

  writeStateToFile("some-path", newg)  // same as the put function call
  return x
}

Здесь random(g) должен возвращать два значения, поэтому я хочу, чтобы он возвращал массив.

Теперь посмотрим, что происходит во время этой последовательности вызовов:

 a = getAny()       same as:    do a <- getAny
 b = getAny()                      b <- getAny
 c = getAny()                      c <- getAny

для a = getAny():

  • состояние читается из файла
  • он передается в random, который возвращает два значения
  • второе значение записывается в файл
  • первое значение возвращается и сохраняется в переменной a

а затем для b = getAny():

  • состояние, только что записанное в файл, считывается обратно в
  • он подается в random(), производя значение и новое состояние
  • новое состояние записывается в файл
  • новое значение возвращается и сохраняется в переменной b

и т.д...

Теперь отвечу на ваши вопросы:

что на самом деле делает put g?

Он обновляет состояние новым значением.

Почему бы на самом деле не потерять g?

newg - это просто локальная переменная, поэтому ее значение будет потеряно, если мы не сохраним его где-нибудь.

person ErikR    schedule 19.06.2016