0

Given a simple list

newtype SomeList = SomeList { items :: [SomeItem] }

and a helper function

newtype SomeListStateT a = SomeListStateT 
  { runSomeListStateT :: StateT SomeList IO a 
  } deriving (Functor, Applicative, Monad, MonadIO)

Can anyone provide a simple example of how to construct a StateT monad transformer to perform CRUD operations against the list please?

I want to add and modify items through Strict State Monad actions.

Unfortunately, the documentation is not so easy to follow (https://docs.w3cub.com/haskell~8/libraries/transformers-0.5.6.2/control-monad-trans-state-strict#g:2)

Any help would be greatly appreciated :)

Yunis
  • 23
  • 5

1 Answers1

1

You can get a lot of this information through GHCi. That is, if you happen to know the various incantations and how to interpret them. Let's start with MonadState:

ghci> :m + Control.Monad.State
ghci> :i MonadState
type MonadState :: * -> (* -> *) -> Constraint
class Monad m => MonadState s m | m -> s where
  get :: m s
  put :: s -> m ()
  state :: (s -> (a, s)) -> m a
  {-# MINIMAL state | get, put #-}
    -- Defined in ‘Control.Monad.State.Class’
instance [safe] Monad m => MonadState s (StateT s m)
  -- Defined in ‘Control.Monad.State.Class’

Ignoring the missing instance for your type for now, we can see that MonadState defines three operations (the CRUD-operations if you will). We can further see that StateT has an instance of MonadState. This means that we can specialise e.g. get:

ghci> :t get @_ @(StateT _ _)
get @_ @(StateT _ _) :: Monad _1 => StateT _2 _1 _2

So we require that the second parameter to StateT must be a monad. This of course matches the instance we saw above. Now you chose to write a newtype-wrapper around StateT SomeList IO which means that you don't have a MonadState instance. You could solve this in two ways:

  1. Use a type synonym in stead of a newtype
  2. Implement an instance. You can derive such an instance with -XGeneralizedNewtypeDeriving

Assuming you create an instance instance MonadState SomeList SomeListStateT you would then see that the three functions specialise to:

ghci> :t get @_ @SomeListStateT
get @_ @SomeListStateT :: SomeListStateT SomeList
ghci> :t put @_ @SomeListStateT
put @_ @SomeListStateT :: SomeList -> SomeListStateT ()
ghci> :t state @_ @SomeListStateT
state @_ @SomeListStateT
  :: (SomeList -> (a, SomeList)) -> SomeListStateT a

If you want to retain your newtype wrapper, you should consider writing a function analogous to:

ghci> :t runStateT
runStateT :: StateT s m a -> s -> m (a, s)

But with the type

SomeListStateT a -> SomeList -> IO (a, SomeList)
fredefox
  • 681
  • 3
  • 11
  • get and put has a type mismatch, i dont know how to unwrap my `newtype SomeListStateT a = SomeListStateT ` on calling evalState get – Yunis Oct 26 '21 at 12:26
  • Hint: What's the type of `runSomeListStateT`? – fredefox Oct 27 '21 at 06:44
  • haha yes i get that but ive tried multiple things to match the specialized get and put monadic actions but i cant get it working... `addItem :: SomeItem -> SomeListStateT Int addItem item = do let state = evalState get [] let list = items (SomeList state) ++ [item] let someList = SomeList { items = list } let newstate = SomeListStateT { runSomeListStateT = someList } execState put newstate return (length list) ` – Yunis Oct 27 '21 at 08:28
  • this is the instance i have declared... `class SomeListMonad m where add :: SomeItem -> m Int instance SomeListMonad SomeListStateT where add = addItem` – Yunis Oct 27 '21 at 08:36
  • `state` allows you to "lift" a stateful function into a state monad. You probably dont want to be running `evalState` in `addItem`. `addItem :: MonadState [a] m => a -> m ()` `addItem x = state (\xs -> ((), x : xs))` – fredefox Oct 27 '21 at 15:18