Условное изменение состояния в Haskell

Я не знаю, как я могу внести условное изменение в State Monad в Haskell. Допустим, у меня есть стек на State Monad.

import Control.Monad.State

push :: Int -> State [Int] ()
push x = state $ \xs -> ((), x : xs)

pop :: State [Int] Int
pop = state $ \(x : xs) -> (x, xs)

И с этим я хочу написать функцию, изменяющую его.

someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do 
  let someValue = x * x
  if b then
    p <- pop
    someValue = someValue + p
  push $ someValue

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

Как я могу добиться такого поведения?


person Heir    schedule 24.11.2020    source источник


Ответы (3)


В Haskell каждому if нужен else, поскольку все является просто значением. Кроме того, вы не можете выполнить if ... then p <- pop ..., так как нотация do теряется в операторе if, поэтому вам нужно перезапустить его с помощью if ... then do p <- pop ....

import Control.Monad.State

push :: a -> State [a] ()
push x = state $ \xs -> ((), x : xs)

pop :: State [a] a
pop = state $ \(x : xs) -> (x, xs)

someFunc :: (Num a) => Bool -> a -> State [a] ()
someFunc b x = do
  let baseValue = x * x
  someValue <- if b then do
        p <- pop
        return $ baseValue + p
      else do
        return baseValue
  push $ someValue
person Aplet123    schedule 24.11.2020
comment
Для монадических действий ifs принципиально не нужны elses — функция when условно выполнит действие. проблема в коде @Heir совсем в другом - см. мой ответ. - person Ari Fordsham; 24.11.2020

Вот самое маленькое исправление:

someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do 
  let someValue = x * x
  if b then
    do { p <- pop
       ; push $ someValue + p }
  else
    push $ someValue

Обе ветви выражения if должны иметь один и тот же тип, а внутри нотации do это должен быть некоторый монадический тип значения, принадлежащий той же монаде, что и весь блок do; в частности, тип всего блока do, если это выражение if является последним в нем.

Смотрите также:

person Will Ness    schedule 24.11.2020

В дополнение к вопросу, который вы задали, как условно выполнить монадическое действие без предложения else, есть еще одна проблема с вашим кодом. Вы хотите переназначить переменную let внутри условного предложения.

Этот вопрос был рассмотрен другими ответами с правильными версиями кода. Я уточню здесь. Все let-переменные неизменяемы, в том числе и внутри монад. Вы можете переопределить переменную в монаде, но это просто скроет исходную переменную от кода ниже того же монадического действия. Переназначение someValue происходит внутри вложенного монадического действия (есть новый блок do) и не видно за пределами if.

Условно переназначить let-переменную принципиально невозможно. Компилятор должен знать во время компиляции, на какое значение ссылается. Способы сделать это - либо return значение из условного выражения, а затем присвоить переменную, например ответ @Aplet123, либо запустить действие в обеих ветвях условного выражения для разных значений, как сказал @Will Ness.

В ответ на заданный вопрос: вы можете условно запустить монадическое действие (без ветви else), используя функцию when из Control.Monad. Итак, если ваш код на самом деле не имеет else ветви, вы можете написать это так:

someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do 
  let someValue = x * x
  when b $ do
    p <- pop
    push $ someValue + p

Конечно, это не имеет смысла для чистых значений, поскольку значение всегда должно быть предоставлено. Но для монадического действия (выдающего (), или у нас та же проблема: каково возвращаемое значение в случае ложного условия?) имеет смысл выполняться по условию.

Просто для любопытства when определяется следующим образом:

when :: (Applicative f) => Bool -> f () -> f ()
when p s  = if p then s else pure ()

Итак, pure () служит «нулевым» монадическим действием.

Счастливого Хаскеллинга!

person Ari Fordsham    schedule 24.11.2020
comment
поэтому, в конце концов, when определяется с помощью if! :) - person Will Ness; 25.11.2020