Reputation:
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.
Upvotes: 1
Views: 140
Reputation: 39366
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.
Upvotes: 0
Reputation: 35099
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.
Upvotes: 1
Reputation: 8153
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))
Upvotes: 0