{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ScopedTypeVariables #-} {-# language RankNTypes #-} module Eve.Internal.States ( States , HasStates(..) , HasEvents , stateLens , makeStateLens ) where import Control.Lens import Data.Map import Unsafe.Coerce import Data.Maybe import Data.Typeable import Data.Default -- | A wrapper to allow storing types of states in the same place. data StateWrapper = forall s. (Typeable s) => StateWrapper s -- | A map of state types to their current value. type States = Map TypeRep StateWrapper -- | Represents a state which can itself store more states. -- 'states' is a lens which points to a given state's 'States' map. class HasStates s where states :: Lens' s States -- | A typeclass to ensure people don't dispatch events to states which shouldn't -- accept them. -- -- To allow dispatching events in an action over your state simply define the -- empty instance: -- -- > instance HasEvents MyState where -- > -- Don't need anything here. class (Typeable s, HasStates s) => HasEvents s -- | A polymorphic lens which accesses stored states. -- It returns the default value ('def') if a state has not yet been set. stateLens :: forall a e. (Typeable a, Default a, HasStates e) => Lens' e a stateLens = lens getter setter where getter s = fromMaybe def $ s ^. states . at (typeRep (Proxy :: Proxy a)) . mapping coerce setter s new = set (states . at (typeRep (Proxy :: Proxy a)) . mapping coerce) (Just new) s coerce = iso (\(StateWrapper x) -> unsafeCoerce x) StateWrapper -- | A utility which creates a state-nested version of a lens. -- If you pass this function a lens from your state to one of its fields, -- it will return a lens which can be used within an 'App' or 'Action'. -- -- The resulting lens will be of type: @newLens :: HasStates s => Lens' s MyState@ -- Or if you prefer, you may wish to specify the state it operates over more specifically -- to prevent using the lens where it was not originally planned. For instance: -- @newLens :: Lens' AppState MyState@ -- -- > data SimpleState = SimpleState -- > { _myString :: String -- > } -- > makeLenses ''SimpleState -- > -- > instance Default SimpleState where -- > def = SimpleState "default" -- > -- > myStringStateLens :: HasStates s => Lens' s String -- > myStringStateLens = makeStateLens myString -- > -- > myAction :: App () -- > myAction = do -- > myStringStateLens .= "Hi!" -- > str <- use myStringStateLens -- > liftIO $ print str -- > -- "Hi!" -- -- For more complex Prisms or Traversals you can write your own using -- 'stateLens' makeStateLens :: (HasStates s, Typeable myState, Default myState) => Lens' myState a -> Lens' s a makeStateLens = (stateLens .)