0

If we have the following two functions, add and subtract, it is simple to chain them to run a series of calculations on an input:

add :: Int -> State Int ()
add n = state $ \x -> ((),x+n)

subtract :: Int -> State Int ()
subtract n = state $ \x -> ((),x-n)

manyOperations :: State Int ()
manyOperations = do
    add 2
    subtract 3
    add 5
    --etc


result = execState manyOperations 5
--result is 9

If we want to print out the state after each calculation is done, we use the StateT monad transformer:

add :: Int -> StateT Int IO ()
add n = StateT $ \x -> print (x+n) >> return ((),x+n)

subtract :: Int -> StateT Int IO ()
subtract n = StateT $ \x -> print (x-n) >> return ((),x-n)

manyOperations :: StateT Int IO ()
manyOperations = do
    add 2
    subtract 3
    add 5

main = runStateT manyOperations 5
-- prints 7, then 4, then 9

Is it possible to replicate this "combined computation and printing" without StateT or any custom datatypes?

As far as I know it's possible to do all the processes that MaybeT IO a can do using IO (Maybe a), but it seems like that's because it's just nested monads. On the other hand, StateT might not have an alternative because s -> (a,s) is fundamentally different to s -> m (a,s)

I can only see two directions the code could go: using State Int (IO ()) or IO (State Int ()), but both of these seem nonsensical given the implementation of StateT

I believe it is impossible. Am I correct?

Note: I know this is completely impractical, but I couldn't find any solution after some hours of work, which means I'm correct or my skills aren't enough for the task.

lightandlight
  • 1,345
  • 3
  • 10
  • 24
  • 2
    Impossible is nothing...but why do you want to do this? That's what `StateT` is *for*. You could always just pass around the `Int` state explicitly , and do everything in `IO ()` alone... – crockeea Jan 30 '15 at 14:47
  • 1
    You can completely avoid those data types by using `s -> IO (a, s)` directly, substituting for `a` and `s` appropriately. It definitely won't be as nice though. – David Young Jan 30 '15 at 17:27
  • It's also worth noting that `State` is defined in terms of `StateT` and not the other way. – David Young Jan 30 '15 at 17:32
  • @DavidYoung Turn that into an answer! – Daniel Wagner Jan 31 '15 at 05:07

3 Answers3

1

Of course you can do all the plumbing yourself. Monads don't add anything new to Haskell, they just enable a ton of code reuse and boilerplate reduction. So anything you can do with a monad, you can laborously do by hand.

manyOperations :: Int -> IO ()
manyOperations n0 = do
    let n1 = n0 + 2
    print n1
    let n2 = n1 - 3
    print n2
    let n3 = n2 + 5
    print n3

If the amount of repetition in the above function is too much for you (it is for me!) you could try to reduce it with a 'combined computation and printing' function but at this point you're bending over backwards to avoid StateT.

-- a bad function, never write something like this!
printAndCompute :: a -> (a -> b) -> IO b
printAndCompute a f = let b = f a in print b >> return b

-- seriously, don't do this!
manyOperations n0 = do
    n1 <- printAndCompute (+2) n0
    n2 <- printAndCompute (-3) n1
    n3 <- printAndCompute (+5) n2
    return ()
cdk
  • 6,698
  • 24
  • 51
0

I'm not sure if this is quite what you're looking for, but you could define an operation which takes a stateful operation that you alread have, and prints out the state after performing the operation -

withPrint :: (Show s) => State s a -> StateT s IO a
withPrint operation = do
    s <- get
    let (a, t) = runState operation s
    liftIO (print t)
    put t
    return a

and then do

manyOperations :: StateT Int IO ()
manyOperations = do
    withPrint (add 2)
    withPrint (subtract 3)
    withPrint (add 5)
    -- etc

This doesn't avoid using StateT but it abstracts out the conversion of your regular, non-IO state operations into IO-ful state operations so you don't have to worry about the details (aside from adding withPrint when you want the state to be printed.

If you don't want the state printed for a particular operation, you could define

withoutPrint :: State s a -> StateT s IO a
withoutPrint operation = do
    s <- get
    let (a, t) = runState operation s
    put t
    return a

which is actually equivalent to

import Control.Monad.Morph

withoutPrint :: State s a -> StateT s IO a
withoutPrint = hoist (\(Identity a) -> return a)

using hoist from Control.Monad.Morph to transform the monad underlying StateT from Identity to IO.

Chris Taylor
  • 46,912
  • 15
  • 110
  • 154
0

You can completely avoid those data types by using s -> IO (a, s) directly, substituting for a and s appropriately. It definitely won't be as nice though.

It would look something like this:

-- This makes StateIO s act as a shorthand for s -> IO (a, s)
type StateIO s a = s -> IO (a, s)

add :: Int -> StateIO Int ()
add n = \x -> print (x+n) >> return ((),x+n)

sub :: Int -> StateIO Int ()
sub n = \x -> print (x-n) >> return ((),x-n)

manyOperations :: StateIO Int ()
manyOperations =   -- Using the definition of (>>=) for StateT
  \s1 -> do        -- and removing a lambda that is immediately applied
      (a, s2) <- add 2 s1
      (a, s3) <- sub 3 s2
      add 5 s3

result :: IO ((), Int)
result = manyOperations 5

The state has to be explicitly threaded through all the operations. This is a major gain we get from using the StateT datatype: the (>>=) method of its Monad instance does all this for us! We can see from this example, though, that there is nothing magical going on in StateT. It's just a very nice way of abstracting out the threading of state.

Also, the relationship between StateT and State is the opposite of the relationship between MaybeT and Maybe: State is defined in terms of StateT. We can see that fact expressed in this type synonym:

type State s = StateT s Identity

It is a transformer over the Identity type (which has the trivial Monad instance).

David Young
  • 10,713
  • 2
  • 33
  • 47