问题:
目前我有一个WorkConfig
类型,看起来像这样
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
(我正在使用PhaseTwo_wc中的String
作为占位符来实际使用的内容)
我有一个函数updateConfig
,它将WorkConfig作为其中一个参数。
问题是我希望能够强制使用哪个构造函数。
例如,在函数phaseOne
中,我希望能够保证在调用updateConfig
时,只能使用PhaseTwo_wc
构造函数。
为了使用类型类进行强制执行,我必须创建单独的数据构造函数,例如:
data PhaseOne_wc = PhaseOne_wc BuildConfig Filename (Maybe XMLFilepath)
如果我走这条路,我还有另外一个问题需要解决。我有其他数据类型WorkConfig
作为值,我该怎么做才能解决这个问题?例如,
type ConfigTracker = TMVar (Map CurrentPhase WorkConfig)
我如何使用类型系统进行我想要的执行,同时记住我上面提到的内容?
ConfigTracker必须能够知道我想要的数据类型。
* 澄清: 我想限制updateConfig可以作为参数使用哪个WorkConfig。
答案 0 :(得分:2)
你的问题有点模糊,所以我会在一般情况下回答。
如果你有一种形式:
data MyType a b c d e f g = C1 a b | C2 c | C3 e f g
...你想要一些适用于所有三个构造函数的函数f
:
f :: MyType a b c d e f g -> ...
...但是你想要一些仅适用于最后一个构造函数的函数g
,那么你有两个选择。
第一个选项是创建嵌入C3
内的第二种类型:
data SecondType e f g = C4 e f g
...然后将其嵌入到原始C3
构造函数中:
data MyType a b c d e f g = C1 a b | C2 c | C3 (SecondType e f g)
...并使g
成为SecondType
的函数:
g :: SecondType e f g -> ...
这只会使f
的代码稍微复杂化,因为您必须首先解压缩C3
才能访问C4
构造函数。
第二个解决方案是,您只需使g
成为C3
构造函数中存储的值的函数:
g :: e -> f -> g -> ...
这不需要修改f
或MyType
类型。
答案 1 :(得分:1)
为了更具体和推动讨论,这个GADT&存在代码可以达到你想要的效果吗?
{-# 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))
答案 2 :(得分:0)
对不起,这是一个扩展的评论而不是一个答案。我有点困惑。听起来你有一些像
这样的功能phaseOne = ...
... (updateConfig ...) ...
phaseTwo = ...
... (updateConfig ...) ...
并且你试图确保例如,在phaseOne
的定义中,这永远不会出现:
phaseOne = ...
... (updateConfig $ PhaseTwo_wc ...) ...
但是现在我问你:updateConfig
是一个纯粹的(非monadic)函数吗?因为如果是,phaseOne
可以很容易地完全正确,并仍然使用updateConfig
调用PhaseTwo_wc
;即它可以扔掉结果(即使它也是monadic):
phaseOne = ...
... (updateConfig $ PhaseTwo_wc ...) `seq` ...
换句话说,我想知道你试图强制执行的约束是否真的是你正在寻找的实际属性?
但是现在,如果我们考虑monad,有一个共同模式,你所描述的是类似类似:制作“特殊”monad,限制可以执行的操作类型;例如
data PhaseOneMonad a = PhaseOnePure a | PhaseOneUpdate BuildConfig Filename (Maybe XMLFilepath) a
instance Monad PhaseOneMonad where ...
这可能是你得到的吗?另请注意,PhaseOneUpdate
不会使用WorkConfig
;它只需要它感兴趣的构造函数参数。这是另一种常见的模式:你不能约束使用哪个构造函数,但你可以直接获取参数。
嗯......但仍不确定。