2
-- InternalComponent.hs
data ComponentState = ComponentState ...
instance Default ComponentState where ...

componentFunction :: (MonadState InternalComponentState m) => a -> m a

-- Program.hs
data ProgramState = forall cs. ProgramState {
    componentState :: cs,
    ...
}

newtype MyMonad a = MyMonad { runMyMonad :: StateT ProgramState IO a }

myFunction a = do
    s <- get
    let cs = componentState s
    let (r, cs') = runState (componentFunction a) cs
    put $ s { componentState = cs' }
    return r

What I want is to be able to use the componentFunction inside of MyMonad (in myFunction, as presented in the example), without being particularly interested in the actual type of the state the component requires. Keeping the component state inside of my own state isn't a requirement, but that's as far as my ability to use state in Haskell goes.

This can really be viewed as an equivalent of an implementation of a stateful interface in another programming language: instantiation of the interface with some implementation provides default state value, and every function called through that interface can modify that state. In any point isn't the user presented with implementation details.

In case it's not clear, the above example fails because the implementation of myFunction can't prove that the record selector provides an appropriate type (because it's an existential); at least that's how I understand it.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
  • 1
    Your `ProgramState` doesn't really make sense. You are saying that there is a field called `componentState`, whose type is completely unknown. You cannot magically "produce" a value of type `ComponentState` from the `componentState` field because you have existentially quantified the type variable `cs` - the type of `cs` is no longer visible to the outside world. You are correct that "myFunction can't prove that the record selector provides an appropriate type". The obvious solution is to not existentially quantify `cs` - it is not clear at all why you would want to in this case. – user2407038 Mar 29 '15 at 19:56
  • 1
    This might be relevant: [Combining multiple states in StateT](http://stackoverflow.com/q/13915923/1333025). – Petr Mar 29 '15 at 21:32

2 Answers2

1

You can parametrize ProgramState by the type of the component state(s), e.g. have

data ProgramState cs = ProgramState { componentState :: cs }

This would mean you'll also have to expose the ComponentState type from InternalComponent.hs, but not the constructors. This way you give the type checker something to play with, but don't expose any internals to the users of InternalComponent.

Waldheinz
  • 10,399
  • 3
  • 31
  • 61
  • That's what I was actually using for the user state in my library. I suppose I could parametrize over both. I think I had some problems with that, though. One of them is having to repeat the type manually everytime. – Bartek Banachewicz Mar 29 '15 at 19:58
  • You can always create a new type when it gets too long, like having `type Program = ProgramState ComponentState` and from there on only use `Program` instead of the expanded type. – Waldheinz Mar 29 '15 at 20:03
  • I've came up with a working example and posted it on [CodeReview](http://codereview.stackexchange.com/questions/85481/did-i-understand-this-module-idea-correctly). – Bartek Banachewicz Mar 31 '15 at 09:58
1

First, I'd suggest to read Combining multiple states in StateT, just to see other available options.

Since in the case of nested states we need to update values inside more complex objects, using lens can make life a lot easier (see also this tutorial). A value of type Lens' s a knows how to reach a particular value of type a inside s and how to modify it (that is, creating a new value of type s that is the same, except for a modified a). Then we can define a helper function

runInside :: (MonadState s m) => Lens' s a -> State a r -> m r
runInside lens s = lens %%= (runState s)

Given a lens and a stateful computation on a, we can lift such a computation to a stateful computation parametrized by s. The library allows us to generate lenses using Template Haskell, for example:

{-# LANGUAGE RankNTypes, TemplateHaskell #-}
import Control.Lens.TH

data ProgramState cs = ProgramState { _componentState :: cs }

$(makeLenses ''ProgramState)

will generate componentState :: Lens' ProgramState cs (actually the generated function will be slightly more more generic). Combining them together we get

runInside componentState :: MonadState (ProgramState a) m => State a r -> m r

Using Typeable we could go even further and create a map that automatically creates or keeps a state for whatever type it is asked for. I'd not recommend this approach in general, as it sort-of avoids Haskell's strong type system checks, but it might be useful in some cases.

{-# LANGUAGE ExistentialQuantification, ScopedTypeVariables, RankNTypes #-}
import Control.Lens
import Control.Lens.TH
import Control.Monad.State
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Typeable

data Something = forall a . Typeable a => Something a

type TypeMap = Map TypeRep Something

We defined a generic untyped container that can hold whatever is Typeable and a map that maps representation of types to their values.

We'll need some class to provide default/starting values:

class Default a where
    getDefault :: a

-- just an example
instance Default Int where
    getDefault = 0

Finally, we can create a lens that given an arbitrary Typeable type, it focuses on its value in the map by looking up its type representation:

typeLens :: forall t . (Typeable t, Default t) => Lens' TypeMap t
typeLens = lens get set
  where
    set map v = Map.insert (typeOf v) (Something v) map
    get map = case Map.lookup (typeRep (Proxy :: Proxy t)) map of
                Just (Something v) | Just r <- cast v   -> r
                _                                       -> getDefault

So you could have TypeMap somewhere in your state and let all stateful computations use it, regardless of what state they need.

However, there is a big warning: If two unrelated computations happen to use the same type for their state, they'll share the value with very likely disastrous results! So using explicit records for states of different parts of your computations is going to be much safer.

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317
  • I'm still in progress of reading the linked answer (thanks for your comment BTW!) I'll read that ASAP. Oh, and forgot to mention I made a deliberate choice of not using Lens in this library (though I might backpedal from that, given how much pain it is to work with regular haskell records). – Bartek Banachewicz Mar 30 '15 at 12:05
  • I've came up with a working example and posted it on [CodeReview](http://codereview.stackexchange.com/questions/85481/did-i-understand-this-module-idea-correctly) – Bartek Banachewicz Mar 31 '15 at 10:20