How can I encode and enforce legal FSM state transitions with a type system? - haskell

Suppose I have a type Thing with a state property A | B | C,
and legal state transitions are A->B, A->C, C->A.
I could write:
transitionToA :: Thing -> Maybe Thing
which would return Nothing if Thing was in a state which cannot transition to A.
But I'd like to define my type, and the transition functions in such a way that transitions can only be called on appropriate types.
An option is to create separate types AThing BThing CThing but that doesn't seem maintainable in complex cases.
Another approach is to encode each state as it's own type:
data A = A Thing
data B = B Thing
data C = C Thing
and
transitionCToA :: C Thing -> A Thing
This seems cleaner to me. But it occurred to me that A,B,C are then functors where all of Things functions could be mapped except the transition functions.
With typeclasses I could create somthing like:
class ToA t where
toA :: t -> A Thing
Which seems cleaner still.
Are there other preferred approaches that would work in Haskell and PureScript?

Here's a fairly simple way that uses a (potentially phantom) type parameter to track which state a Thing is in:
{-# LANGUAGE DataKinds, KindSignatures #-}
-- note: not exporting the constructors of Thing
module Thing (Thing, transAB, transAC, transCA) where
data State = A | B | C
data Thing (s :: State) = {- elided; can even be a data family instead -}
transAB :: Thing A -> Thing B
transAC :: Thing A -> Thing C
transCA :: Thing C -> Thing A
transAB = {- elided -}
transAC = {- elided -}
transCA = {- elided -}

You could use a type class (available in PureScript) along with phantom types as John suggested, but using the type class as a final encoding of the type of paths:
data A -- States at the type level
data B
data C
class Path p where
ab :: p A B -- One-step paths
ac :: p A C
ca :: p C A
trans :: forall a b c. p c b -> p b a -> p c a -- Joining paths
refl :: forall a. p a a
Now you can create a type of valid paths:
type ValidPath a b = forall p. (Path p) => p a b
roundTrip :: ValidPath A A
roundTrip = trans ca ac
Paths can only be constructed by using the one-step paths you provide.
You can write instances to use your paths, but importantly, any instance has to respect the valid transitions at the type level.
For example, here is an interpretation which calculates lengths of paths:
newtype Length = Length Int
instance pathLength :: Path Length where
ab = Length 1
ac = Length 1
ca = Length 1
trans (Length n) (Length m) = Length (n + m)
refl = Length 0

Since your goal is to prevent developers from performing illegal transitions, you may want to look into phantom types. Phantom types allow you to model type-safe transitions without leveraging more advanced features of the type system; as such they are portable to many languages.
Here's a PureScript encoding of your above problem:
foreign import data A :: *
foreign import data B :: *
foreign import data C :: *
data Thing a = Thing
transitionToA :: Thing C -> Thing A
Phantom types work well to model valid state transitions when you have the property that two different states cannot transition to the same state (unless all states can transition to that state). You can workaround this limitation by using type classes (class CanTransitionToA a where trans :: Thing a -> Thing A), but at this point, you should investigate other approaches.

If you want to store a list of transitions so that you can process it later, you can do something like this:
{-# LANGUAGE DataKinds, GADTs, KindSignatures, PolyKinds #-}
data State = A | B | C
data Edge (a :: State) (b :: State) where
EdgeAB :: Edge A B
EdgeAC :: Edge A C
EdgeCA :: Edge C A
data Domino (f :: k -> k -> *) (a :: k) (b :: k) where
I :: Domino f a a
(:>>:) :: f a b -> Domino f b c -> Domino f a c
infixr :>>:
example :: Domino Edge A B
example = EdgeAC :>>: EdgeCA :>>: EdgeAB :>>: I
You can turn that into an instance of Path by writing a concatenation function for Domino:
{-# LANGUAGE FlexibleInstances #-}
instance Path (Domino Edge) where
ab = EdgeAB :>>: I
ac = EdgeAC :>>: I
ca = EdgeCA :>>: I
refl = I
trans I es' = es'
trans (e :>>: es) es' = e :>>: (es `trans` es')
In fact, this makes me wonder if Hackage already has a package that defines "indexed monoids":
class IMonoid (m :: k -> k -> *) where
imempty :: m a a
imappend :: m a b -> m b c -> m a c
instance IMonoid (Domino e) where
imempty = I
imappend I es' = es'
imappend (e :>>: es) es' = e :>>: (es `imappend` es')

Related

Resolving ambiguity in MultiParameterTypeClass

Based on the feedback to this question, I've used a MultiParamTypeClass to represent a reinforcement learning environment Environment, using 3 type variables: e for the environment instance itself (e.g. a game like Nim, below), s for the state data type used by the specific game, and a for the action data type used by the specific game.
{-# LANGUAGE MultiParamTypeClasses #-}
class MultiAgentEnvironment e s a where
baseState :: e -> s
nextState :: e -> s -> a -> s
reward :: (Num r) => e -> s -> a -> [r]
data Game = Game { players :: Int
, initial_piles :: [Int]
} deriving (Show)
data State = State { player :: Int
, piles :: [Int]} deriving (Show)
data Action = Action { removed :: [Int]} deriving (Show)
instance MultiAgentEnvironment Game State Action where
baseState game = State{player=0, piles=initial_piles game}
nextState game state action = State{player=player state + 1 `mod` players game,
piles=zipWith (-) (piles state) (removed action)}
reward game state action = [0, 0]
newGame :: Int -> [Int] -> Game
newGame players piles = Game{players=players, initial_piles=piles}
main = do
print "Hello, world!"
let game = newGame 2 [3,4,5]
print game
As expected, I'm running into ambiguity issues already. See below, where the action type variable a is deemed ambiguous within the typeclass Environment.
(base) randm#soundgarden:~/Projects/games/src/Main$ ghc -o basic basic.hs
[1 of 1] Compiling Main ( basic.hs, basic.o )
basic.hs:4:5: error:
• Could not deduce (MultiAgentEnvironment e s a0)
from the context: MultiAgentEnvironment e s a
bound by the type signature for:
baseState :: forall e s a. MultiAgentEnvironment e s a => e -> s
at basic.hs:4:5-23
The type variable ‘a0’ is ambiguous
• In the ambiguity check for ‘baseState’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
baseState :: forall e s a. MultiAgentEnvironment e s a => e -> s
In the class declaration for ‘MultiAgentEnvironment’
|
4 | baseState :: e -> s
| ^^^^^^^^^^^^^^^^^^^
How do I resolve this ambiguity? Am I mistakenly using typeclasses to implement interfaces (i.e. baseState, nextState, reward)?
While you could turn on AllowAmbiguousTypes, that will only push your problem further down the road. That is, eventually, you'll try to call baseState, and GHC will need to know what a is. There are three good options you have:
Use functional dependencies,
Use associated type families, or
Don't use a class for this.
Let's look at each option in detail.
Functional Dependencies
The problem that GHC is having is that it knows which e and s you want to use when you call a function like baseState (it can determine those from the input and output of the baseState function), but it doesn't know which a to use. For all GHC knows, there may be multiple instantiations of MultiAgentEnvironment that could work for a given e and s. With functional dependencies, you can tell GHC that a given e and s totally define what the a should be. In plain English, by putting in a functional dependency on a, you're saying that for any given environment and state, there is only one possible action type that makes sense. If this is true, then you add them like so:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
class MultiAgentEnvironment e s a | e s -> a where
baseState :: e -> s
nextState :: e -> s -> a -> s
reward :: (Num r) => e -> s -> a -> [r]
Since each one of your functions have e and s in them, a is the only type parameter that could have been ambiguous, and with this fundep, it's not ambiguous anymore. That said, if you know that the environment type determines both the state and action unambiguously (that is, a given environment can only ever have one possible state and action type), then you can use two fundeps to lower the ambiguity even more, as in:
class MultiAgentEnvironment e s a | e -> s a where
Associated Type Families
Functional Dependencies have a bit of a bad reputation, and the more modern alternative is typically to use associated type families. In practice, for your purposes, they work very similarly. Once again, you have to be okay with the environment type determining the action type (and, perhaps, the state type). If so, you write it like this:
{-# LANGUAGE TypeFamilies #-}
class MultiAgentEnvironment e where
type EState e
type EAction e
baseState :: e -> EState e
nextState :: e -> EState e -> a -> EState e
reward :: (Num r) => e -> EState e -> EAction e -> [r]
Then, when you create your instance, it will look like:
instance MultiAgentEnvironment Game where
type EState Game = State
type EAction Game = Action
baseState game = State{player=0, piles=initial_piles game}
nextState game state action = ...
Use a Data Type Instead Of a Class
The last option is to forgo using a type class altogether. Instead, you can make the data explicit by just representing it as a data type. For instance, you can define:
{-# LANGUAGE RankNTypes #-}
data MultiAgentEnvironment e s a = MultiAgentEnvironment
{ baseState :: e -> s
, nextState :: e -> s -> a -> s
, reward :: forall r. (Num r) => e -> s -> a -> [r]
}
Instead of making an instance of the type class, you just make a value of the data type:
gameStateActionMAE :: MultiAgentEnvironment Game State Action
gameStateActionMAE = MultiAgentEnvironment
{ baseState = \game -> State{player=0, piles=initial_piles game}
, nextState = \game state action -> State{player=player state + 1 `mod` players game,
piles=zipWith (-) (piles state) (removed action)}
, reward = \game state action -> [0, 0]
}
One nice advantage of this method is that you can make multiple different MultiAgentEnvironments with the same types but that have different behaviors. Using them is pretty simple too: instead of having MultiAgentEnvironment e s a as a constraint, you now have it as just a regular old argument. In fact, if you turn on the RecordWildCards pragma, then any function that used to start with
foo :: MultiAgentEnvironment e s a => x -> y
foo x = ...
can now be written as
foo :: MultiAgentEnvironment e s a -> x -> y
foo mae#MultiAgentEnvironment{..} x = ...
and the body should be pretty much identical (well, unless the body calls a subfunction which requires the MultiAgentEnvironment, in which case you'll need to manually pass mae along).

Creating a result piecewise from stateful computation, with good ergonomics

I'd like to write a function
step :: State S O
where O is a record type:
data O = MkO{ out1 :: Int, out2 :: Maybe Int, out3 :: Maybe Bool }
The catch is that I'd like to assemble my O output piecewise. What I mean by that, is that at various places along the definition of step, I learn then and there that e.g. out2 should be Just 3, but I don't know in a non-convoluted way what out1 and out3 should be. Also, there is a natural default value for out1 that can be computed from the end state; but there still needs to be the possibility to override it in step.
And, most importantly, I want to "librarize" this, so that users can provide their own S and O types, and I give them the rest.
My current approach is to wrap everything in a WriterT (HKD O Last) using Higgledy's automated way of creating a type HKD O Last which is isomorphic to
data OLast = MkOLast{ out1' :: Last Int, out2' :: Last (Maybe Int), out3' :: Last (Maybe String) }
This comes with the obvious Monoid instance, so I can, at least morally, do the following:
step = do
MkOLast{..} <- execWriterT step'
s <- get
return O
{ out1 = fromMaybe (defaultOut1 s) $ getLast out1'
, out2 = getLast out2'
, out3 = fromMaybe False $ getLast out3'
}
step' = do
...
tell mempty{ out2' = pure $ Just 42 }
...
tell mempty{ out1' = pure 3 }
This is code I could live with.
The problem is that I can only do this morally. In practice, what I have to write is quite convoluted code because Higgledy's HKD O Last exposes record fields as lenses, so the real code ends up looking more like the following:
step = do
oLast <- execWriterT step'
s <- get
let def = defaultOut s
return $ runIdentity . construct $ bzipWith (\i -> maybe i Identity . getLast) (deconstruct def) oLast
step' = do
...
tell $ set (field #"out2") (pure $ Just 42) mempty
...
tell $ set (field #"out3") (pure 3) mempty
The first wart in step we can hide away behind a function:
update :: (Generic a, Construct Identity a, FunctorB (HKD a), ProductBC (HKD a)) => a -> HKD a Last -> a
update initial edits = runIdentity . construct $ bzipWith (\i -> maybe i Identity . getLast) (deconstruct initial) edits
so we can "librarize" that as
runStep
:: (Generic o, Construct Identity o, FunctorB (HKD o), ProductBC (HKD o))
=> (s -> o) -> WriterT (HKD o Last) (State s) () -> State s o
runStep mkDef step = do
let updates = execWriterT step s
def <- gets mkDef
return $ update def updates
But what worries me are the places where partial outputs are recorded. So far, the best I've been able to come up with is to use OverloadedLabels to provide #out2 as a possible syntax:
instance (HasField' field (HKD a f) (f b), Applicative f) => IsLabel field (b -> Endo (HKD a f)) where
fromLabel x = Endo $ field #field .~ pure x
output :: (Monoid (HKD o Last)) => Endo (HKD o Last) -> WriterT (HKD o Last) (State s) ()
output f = tell $ appEndo f mempty
this allows end-users to write step' as
step' = do
...
output $ #out2 (Just 42)
...
output $ #out3 3
but it's still a bit cumbersome; moreover, it uses quite a lot of heavy machinery behind the scenes. Especially given that my use case is such that all the library internals would need to be explained step-by-step.
So, what I am looking for are improvements in the following areas:
Simpler internal implementation
Nicer API for end-users
I'd be happy with a completely different approach from first principles as well, as long as it doesn't require the user to define their own OLast next to O...
The following is not a very satisfactory solution because it's still complex and the type errors are horrific, but it tries to achieve two things:
Any attempt to "complete" the construction of the record without having specified all mandatory fields results in a type error.
"there is a natural default value for out1 that can be computed from the end state; but there still needs to be the possibility to override it"
The solution does away with the State monad. Instead, there's an extensible record to which new fields are progressively added—therefore changing its type—until it is "complete".
We use the red-black-record, sop-core (these for HKD-like functionality) and transformers (for the Reader monad) packages.
Some necessary imports:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
import Data.RBR (Record,unit,FromRecord(fromRecord),ToRecord,RecordCode,
Productlike,fromNP,toNP,ProductlikeSubset,projectSubset,
FromList,
Insertable,Insert,insert) -- from "red-black-record"
import Data.SOP (I(I),unI,NP,All,Top) -- from "sop-core"
import Data.SOP.NP (sequence_NP)
import Data.Function (fix)
import Control.Monad.Trans.Reader (Reader,runReader,reader)
import qualified GHC.Generics
The datatype-generic machinery:
specify :: forall k v t r. Insertable k v t
=> v -> Record (Reader r) t -> Record (Reader r) (Insert k v t)
specify v = insert #k #v #t (reader (const v))
close :: forall r subset subsetflat whole . _ => Record (Reader r) whole -> r
close = fixRecord #r #subsetflat . projectSubset #subset #whole #subsetflat
where
fixRecord
:: forall r flat. (FromRecord r, Productlike '[] (RecordCode r) flat, All Top flat)
=> Record (Reader r) (RecordCode r)
-> r
fixRecord = unI . fixHelper I
fixHelper
:: forall r flat f g. _
=> (NP f flat -> g (NP (Reader r) flat))
-> Record f (RecordCode r)
-> g r
fixHelper adapt r = do
let moveFunctionOutside np = runReader . sequence_NP $ np
record2record np = fromRecord . fromNP <$> moveFunctionOutside np
fix . record2record <$> adapt (toNP r)
specify adds a field to an extensible HKD-like record where each field is actually a function from the completed record to the type of the field in the completed record. It inserts the field as a constant function. It can also override existing default fields.
close takes an extensible record constructed with specify and "ties the knot", returning the completed non-HKD record.
Here's code that must be written for each concrete record:
data O = MkO { out1 :: Int, out2 :: Maybe Int, out3 :: Maybe Bool }
deriving (GHC.Generics.Generic, Show)
instance FromRecord O
instance ToRecord O
type ODefaults = FromList '[ '("out1",Int) ]
odefaults :: Record (Reader O) ODefaults
odefaults =
insert #"out1" (reader $ \r -> case out2 r of
Just i -> succ i
Nothing -> 0)
$ unit
In odefaults we specify overrideable default values for some fields, which are calculated by inspecting the "completed" record (this works because we later tie the knot with close.)
Putting it all to work:
example1 :: O
example1 =
close
. specify #"out3" (Just False)
. specify #"out2" (Just 0)
$ odefaults
example2override :: O
example2override =
close
. specify #"out1" (12 :: Int)
. specify #"out3" (Just False)
. specify #"out2" (Just 0)
$ odefaults
main :: IO ()
main =
do print $ example1
print $ example2override
-- result:
-- MkO {out1 = 1, out2 = Just 0, out3 = Just False}
-- MkO {out1 = 12, out2 = Just 0, out3 = Just False}
Here's what I am currently using for this: basically the same Barbies-based technique from my original question, but using barbies-th and lens to create properly named field lenses.
I am going to illustrate it with an example. Suppose I want to collect this result:
data CPUOut = CPUOut
{ inputNeeded :: Bool
, ...
}
Create Barbie for CPUOut using barbies-th, add _ prefix to field names, and use lens's makeLenses TH macro to generate field accessors:
declareBareB [d|
data CPUOut = CPUOut
{ _inputNeeded :: Bool
, ...
} |]
makeLenses ''CPUState
Write update s.t. it works on partial values that are wrapped in the Barbie newtype wrapper:
type Raw b = b Bare Identity
type Partial b = Barbie (b Covered) Last
update
:: (BareB b, ApplicativeB (b Covered))
=> Raw b -> Partial b -> Raw b
update initials edits =
bstrip $ bzipWith update1 (bcover initials) (getBarbie edits)
where
update1 :: Identity a -> Last a -> Identity a
update1 initial edit = maybe initial Identity (getLast edit)
The role of the Barbie wrapper is that Barbie b f has a Monoid instance if only all the fields of b f are monoids themselves. This is exactly the case for Partial CPUOut, so that is what we are going to be collecting in our WriterT:
type CPU = WriterT (Partial CPUOut) (State CPUState)
Write the generic output assignment combinator. This is what makes it nicer than the approach in the original question, because the Setter's are properly named field accessor lenses, not overloaded labels:
(.:=)
:: (Applicative f, MonadWriter (Barbie b f) m)
=> Setter' (b f) (f a) -> a -> m ()
fd .:= x = scribe (iso getBarbie Barbie . fd) (pure x)
Example use:
startInput :: CPU ()
startInput = do
inputNeeded .:= True
phase .= WaitInput

How to use the same record selector two ways within a function? Lenses?

I have some data that have different representations based on a type parameter, a la Sandy Maguire's Higher Kinded Data. Here are two examples:
wholeMyData :: MyData Z
wholeMyData = MyData 1 'w'
deltaMyData :: MyData Delta
deltaMyData = MyData Nothing (Just $ Left 'b')
I give some of the implementation details below, but first the actual question.
I often want to get a field of the data, usually via a local definition like:
let x = either (Just . Left . myDataChar) myDataChar -- myDataChar a record of MyData
It happens so often I would like to make a standard combinator,
getSubDelta :: ( _ -> _ ) -> Either a b -> Maybe (Either c d)
getSubDelta f = either (Just . Left . f) f
but filling in that signature is problematic. The easy solution is to just supply the record selector function twice,
getSubDelta :: (a->c) -> (b->d) -> Either a b -> Maybe (Either c d)
getSubDelta f g = either (Just . Left . f) g
but that is unseemly. So my question. Is there a way I can fill in the signature above? I'm assuming there is probably a lens based solution, what would that look like? Would it help with deeply nested data? I can't rely on the data types always being single constructor, so prisms? Traversals? My lens game is weak, so I was hoping to get some advice before I proceed.
Thanks!
Some background. I defined a generic method of performing "deltas", via a mix of GHC.Generics and type families. The gist is to use a type family in the definition of the data type. Then, depending how the type is parameterized, the records will either represent whole data or a change to existing data.
For instance, I define the business data using DeltaPoints.
MyData f = MyData { myDataInt :: DeltaPoint f Int
, myDataChar :: DeltaPoint f Char} deriving Generic
The DeltaPoints are implemented in the library, and have different forms for Delta and Z states.
data DeltaState = Z | Delta deriving (Show,Eq,Read)
type family DeltaPoint (st :: DeltaState) a where
DeltaPoint Z a = a
DeltaPoint Delta a = Maybe (Either a (DeltaOf a))
So a DeltaPoint Z a is just the original data, a, and a DeltaPoint Delta a, may or may not be present, and if it is present will either be a replacement of the original (Left) or an update (DeltaOf a).
The runtime delta functionality is encapsulated in a type class.
class HasDelta a where
type DeltaOf a
delta :: a -> a -> Maybe (Either a (DeltaOf a))
applyDeltaOf :: a -> DeltaOf a -> Maybe a
And with the use of Generics, I can usually get the delta capabilities with something like:
instance HasDelta (MyData Z) where
type (DeltaOf (MyData Z)) = MyData Delta
I think you probably want:
{-# LANGUAGE RankNTypes #-}
getSubDelta :: (forall f . (dat f -> DeltaPoint f fld))
-> Either (dat Z) (dat Delta)
-> Maybe (Either (DeltaPoint Z fld) (DeltaOf fld))
getSubDelta sel = either (Just . Left . sel) sel
giving:
x :: Either (MyData Z) (MyData Delta)
-> Maybe (Either (DeltaPoint Z Char) (DeltaOf Char))
x = getSubDelta myDataChar
-- same as: x = either (Just . Left . myDataChar) myDataChar

Combining multiple states in StateT

I am writing a program that runs as a daemon.
To create the daemon, the user supplies a set of
implementations for each of the required classes (one of them is a database)
All of these classes have functions have
type signatures of the form StateT s IO a,
but s is different for each class.
Suppose each of the classes follows this pattern:
import Control.Monad (liftM)
import Control.Monad.State (StateT(..), get)
class Hammer h where
driveNail :: StateT h IO ()
data ClawHammer = MkClawHammer Int -- the real implementation is more complex
instance Hammer ClawHammer where
driveNail = return () -- the real implementation is more complex
-- Plus additional classes for wrenches, screwdrivers, etc.
Now I can define a record that represents the implementation chosen by
the user for each "slot".
data MultiTool h = MultiTool {
hammer :: h
-- Plus additional fields for wrenches, screwdrivers, etc.
}
And the daemon does most of its work in the StateT (MultiTool h ...) IO ()
monad.
Now, since the multitool contains a hammer, I can use it in any situation
where a hammer is needed. In other words, the MultiTool type
can implement any of the classes it contains, if I write code like this:
stateMap :: Monad m => (s -> t) -> (t -> s) -> StateT s m a -> StateT t m a
stateMap f g (StateT h) = StateT $ liftM (fmap f) . h . g
withHammer :: StateT h IO () -> StateT (MultiTool h) IO ()
withHammer runProgram = do
t <- get
stateMap (\h -> t {hammer=h}) hammer runProgram
instance Hammer h => Hammer (MultiTool h) where
driveNail = withHammer driveNail
But the implementations of withHammer, withWrench, withScrewdriver, etc.
are basically identical. It would be nice to be able to write something
like this...
--withMember accessor runProgram = do
-- u <- get
-- stateMap (\h -> u {accessor=h}) accessor runProgram
-- instance Hammer h => Hammer (MultiTool h) where
-- driveNail = withMember hammer driveNail
But of course that won't compile.
I suspect my solution is too object-oriented.
Is there a better way?
Monad transformers, maybe?
Thank you in advance for any suggestions.
If you want to go with a large global state like in your case, then what you want to use is lenses, as suggested by Ben. I too recommend Edward Kmett's lens library. However, there is another, perhaps nicer way.
Servers have the property that the program runs continuously and performs the same operation over a state space. The trouble starts when you want to modularize your server, in which case you want more than just some global state. You want modules to have their own state.
Let's think of a module as something that transforms a Request to a Response:
Module :: (Request -> m Response) -> Module m
Now if it has some state, then this state becomes noticable in that the module might give a different answer the next time. There are a number of ways to do this, for example the following:
Module :: s -> ((Request, s) -> m (Response s)) -> Module m
But a much nicer and equivalent way to express this is the following constructor (we will build a type around it soon):
Module :: (Request -> m (Response, Module m)) -> Module m
This module maps a request to a response, but along the way also returns a new version of itself. Let's go further and make requests and responses polymorphic:
Module :: (a -> m (b, Module m a b)) -> Module m a b
Now if the output type of a module matches another module's input type, then you can compose them like regular functions. This composition is associative and has a polymorphic identity. This sounds a lot like a category, and in fact it is! It is a category, an applicative functor and an arrow.
newtype Module m a b =
Module (a -> m (b, Module m a b))
instance (Monad m) => Applicative (Module m a)
instance (Monad m) => Arrow (Module m)
instance (Monad m) => Category (Module m)
instance (Monad m) => Functor (Module m a)
We can now compose two modules that have their own individual local state without even knowing about it! But that's not sufficient. We want more. How about modules that can be switched among? Let's extend our little module system such that modules can actually choose not to give an answer:
newtype Module m a b =
Module (a -> m (Maybe b, Module m a b))
This allows another form of composition that is orthogonal to (.): Now our type is also a family of Alternative functors:
instance (Monad m) => Alternative (Module m a)
Now a module can choose whether to respond to a request, and if not, the next module will be tried. Simple. You have just reinvented the wire category. =)
Of course you don't need to reinvent this. The Netwire library implements this design pattern and comes with a large library of predefined "modules" (called wires). See the Control.Wire module for a tutorial.
Here's a concrete example of how to use lens like everybody else is talking about. In the following code example, Type1 is the local state (i.e. your hammer), and Type2 is the global state (i.e. your multitool). lens provides the zoom function which lets you run a localized state computation that zooms in on any field defined by a lens:
import Control.Lens
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State
data Type1 = Type1 {
_field1 :: Int ,
_field2 :: Double}
field1 :: SimpleLens Type1 Int
field1 = lens _field1 (\x a -> x { _field1 = a})
field2 :: SimpleLens Type1 Double
field2 = lens _field2 (\x a -> x { _field2 = a})
data Type2 = Type2 {
_type1 :: Type1 ,
_field3 :: String}
type1 :: SimpleLens Type2 Type1
type1 = lens _type1 (\x a -> x { _type1 = a})
field3 :: SimpleLens Type2 String
field3 = lens _field3 (\x a -> x { _field3 = a})
localCode :: StateT Type1 IO ()
localCode = do
field1 += 3
field2 .= 5.0
lift $ putStrLn "Done!"
globalCode :: StateT Type2 IO ()
globalCode = do
f1 <- zoom type1 $ do
localCode
use field1
field3 %= (++ show f1)
f3 <- use field3
lift $ putStrLn f3
main = runStateT globalCode (Type2 (Type1 9 4.0) "Hello: ")
zoom is not limited to immediate sub-fields of a type. Since lenses are composable, you can zoom as deep as you want in a single operation just by doing something like:
zoom (field1a . field2c . field3b . field4j) $ do ...
This sounds very much like an application of lenses.
Lenses are a specification of a sub-field of some data. The idea is you have some value toolLens and functions view and set so that view toolLens :: MultiTool h -> h fetches the tool and set toolLens :: MultiTool h -> h -> MultiTool h replaces it with a new value. Then you can easily define your withMember as a function just accepting a lens.
Lens technology has advanced a great deal recently, and they are now incredibly capable. The most powerful library around at the time of writing is Edward Kmett's lens library, which is a bit much to swallow, but pretty simple once you find the features you want. You can also search for more questions about lenses here on SO, e.g. Functional lenses which links to lenses, fclabels, data-accessor - which library for structure access and mutation is better, or the lenses tag.
I created a lensed extensible record library called data-diverse-lens which allows combining multiple ReaderT (or StateT) like this gist:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
module Main where
import Control.Lens
import Control.Monad.Reader
import Control.Monad.State
import Data.Diverse.Lens
import Data.Semigroup
foo :: (MonadReader r m, HasItem' Int r, HasItem' String r) => m (Int, String)
foo = do
i <- view (item' #Int) -- explicitly specify type
s <- view item' -- type can also be inferred
pure (i + 10, s <> "bar")
bar :: (MonadState s m, HasItem' Int s, HasItem' String s) => m ()
bar = do
(item' #Int) %= (+10) -- explicitly specify type
item' %= (<> "bar") -- type can also be inferred
pure ()
main :: IO ()
main = do
-- example of running ReaderT with multiple items
(i, s) <- runReaderT foo ((2 :: Int) ./ "foo" ./ nil)
putStrLn $ show i <> s -- prints out "12foobar"
-- example of running StateT with multiple items
is <- execStateT bar ((2 :: Int) ./ "foo" ./ nil)
putStrLn $ show (view (item #Int) is) <> (view (item #String) is) -- prints out "12foobar"
Data.Has is a simpler library that does the same with tuples. Example from the library front page:
{-# LANGUAGE FlexibleContexts #-}
-- in some library code
...
logInAnyReaderHasLogger :: (Has Logger r, MonadReader r m) => LogString -> m ()
logInAnyReaderHasLogger s = asks getter >>= logWithLogger s
queryInAnyReaderHasSQL :: (Has SqlBackEnd r, MonadReader r m) => Query -> m a
queryInAnyReaderHasSQL q = asks getter >>= queryWithSQL q
...
-- now you want to use these effects together
...
logger <- initLogger ...
sql <- initSqlBackEnd ...
(`runReader` (logger, sql)) $ do
...
logInAnyReaderHasLogger ...
...
x <- queryInAnyReaderHasSQL ...
...

Challenged by types

The problem:
Currently I have a type WorkConfig, which looks like this
data WorkConfig = PhaseZero_wc BuildConfig
| PhaseOne_wc BuildConfig Filename (Maybe XMLFilepath)
| PhaseTwo_wc String
| SoulSucker_wc String
| ImageInjector_wc String
| ESX_wc String
| XVA_wc String
| VNX_wc String
| HyperV_wc String
| Finish_wc String
deriving Show
(I'm using String from PhaseTwo_wc on as a placeholder for what will actually be used)
I have a function updateConfig that takes a WorkConfig as one of it's parameters.
The problem is that I want to be able to enforce which constructor is used.
For example in the function phaseOne I want to be able to guarantee that when updateConfig is invoked, only the PhaseTwo_wc constructor can be used.
In order to use a type class for this enforcement, I would have to make separate data constructors, for example:
data PhaseOne_wc = PhaseOne_wc BuildConfig Filename (Maybe XMLFilepath)
If I go this route, I have another problem to solve. I have other data types that have WorkConfig as a value, what would I do to address this? For example,
type ConfigTracker = TMVar (Map CurrentPhase WorkConfig)
How can I use the type system for the enforcement I would like, while keeping in mind what I mentioned above?
ConfigTracker would have to be able to know which data type I wanted.
* Clarification:
I'm looking to restrict which WorkConfig that updateConfig may take as a parameter.
Your question was a little vague, so I will answer in the general.
If you have a type of the form:
data MyType a b c d e f g = C1 a b | C2 c | C3 e f g
... and you want some function f that works on all three constructors:
f :: MyType a b c d e f g -> ...
... but you want some function g that works on just the last constructor, then you have two choices.
The first option is to create a second type embedded within C3:
data SecondType e f g = C4 e f g
... and then embed that within the original C3 constructor:
data MyType a b c d e f g = C1 a b | C2 c | C3 (SecondType e f g)
... and make g a function of SecondType:
g :: SecondType e f g -> ...
This only slightly complicates the code for f as you will have to first unpack C3 to access the C4 constructor.
The second solution is that you just make g a function of the values stored in the C3 constructor:
g :: e -> f -> g -> ...
This requires no modification to f or the MyType type.
To be more concrete and drive the discussion, how close does this GADT & Existential code get to what you want?
{-# LANGUAGE GADTs, KindSignatures, DeriveDataTypeable, ExistentialQuantification, ScopedTypeVariables, StandaloneDeriving #-}
module Main where
import qualified Data.Map as Map
import Data.Typeable
import Data.Maybe
data Phase0 deriving(Typeable)
data Phase1 deriving(Typeable)
data Phase2 deriving(Typeable)
data WC :: * -> * where
Phase0_ :: Int -> WC Phase0
Phase1_ :: Bool -> WC Phase1
deriving (Typeable)
deriving instance Show (WC a)
data Some = forall a. Typeable a => Some (WC a)
deriving instance Show Some
things :: [Some]
things = [ Some (Phase0_ 6) , Some (Phase1_ True) ]
do'phase0 :: WC Phase0 -> WC Phase1
do'phase0 (Phase0_ i) = Phase1_ (even i)
-- Simplify by using TypeRep of the Phase* types as key
type M = Map.Map TypeRep Some
updateConfig :: forall a. Typeable a => WC a -> M -> M
updateConfig wc m = Map.insert key (Some wc) m
where key = typeOf (undefined :: a)
getConfig :: forall a. Typeable a => M -> Maybe (WC a)
getConfig m = case Map.lookup key m of
Nothing -> Nothing
Just (Some wc) -> cast wc
where key = typeOf (undefined :: a)
-- Specialization of updateConfig restricted to taking Phase0
updateConfig_0 :: WC Phase0 -> M -> M
updateConfig_0 = updateConfig
-- Example of processing from Phase0 to Phase1
process_0_1 :: WC Phase0 -> WC Phase1
process_0_1 (Phase0_ i) = (Phase1_ (even i))
main = do
print things
let p0 = Phase0_ 6
m1 = updateConfig p0 Map.empty
m2 = updateConfig (process_0_1 p0) m1
print m2
print (getConfig m2 :: Maybe (WC Phase0))
print (getConfig m2 :: Maybe (WC Phase1))
print (getConfig m2 :: Maybe (WC Phase2))
Sorry, this is more of an extended comment than an answer. I'm a little confused. It sounds like you have some functions like
phaseOne = ...
... (updateConfig ...) ...
phaseTwo = ...
... (updateConfig ...) ...
and you're trying to make sure that eg, inside the definition of phaseOne, this never appears:
phaseOne = ...
... (updateConfig $ PhaseTwo_wc ...) ...
But now I ask you: is updateConfig a pure (non-monadic) function? Because if it is, than phaseOne can easily be perfectly correct and still invoke updateConfig with a PhaseTwo_wc; ie it could just throw away the result (and even if it's monadic too):
phaseOne = ...
... (updateConfig $ PhaseTwo_wc ...) `seq` ...
In other words, I'm wondering if the constraint you're trying to enforce is really the actual property you are looking for?
But now, if we're thinking of monads, there is a common pattern that what you describe is sort of like: making "special" monads that limit the kind of actions that can be performed; eg
data PhaseOneMonad a = PhaseOnePure a | PhaseOneUpdate BuildConfig Filename (Maybe XMLFilepath) a
instance Monad PhaseOneMonad where ...
Is this maybe what you're getting at? Note also that PhaseOneUpdate doesn't take a WorkConfig; it just takes the constructor parameters it is interested in. This is another common pattern: you can't constrain which constructor is used, but you can just take the arguments directly.
Hm... still not sure though.

Resources