1

问题:

目前我有一个类型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

(我使用StringPhaseTwo_wc on 作为实际使用的占位符)

我有一个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 作为参数。

4

3 回答 3

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 -> ...

这不需要修改fMyType类型。

于 2012-07-20T02:17:44.257 回答
1

更具体地说,推动讨论,这个 GADT 和 Existential 代码与你想要的有多接近?

{-# 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))
于 2012-07-20T02:17:14.310 回答
0

抱歉,这更像是一个扩展评论而不是答案。我有点困惑。听起来你有一些功能,比如

phaseOne = ...
           ... (updateConfig ...) ...

phaseTwo = ...
           ... (updateConfig ...) ...

并且您正在尝试确保,例如,在 的定义中phaseOne,这永远不会出现:

phaseOne = ...
           ... (updateConfig $ PhaseTwo_wc ...) ...

但是现在我问你:是updateConfig纯(非单子)函数吗?因为如果是的话,thanphaseOne可以很容易地完全正确并且仍然updateConfig使用PhaseTwo_wc;调用。即它可能会丢弃结果(即使它也是一元的):

phaseOne = ...
           ... (updateConfig $ PhaseTwo_wc ...) `seq` ...

换句话说,我想知道您要强制执行的约束是否真的是您正在寻找的实际属性?

但是现在,如果我们考虑单子,有一个常见的模式,你所描述的有点像:制作“特殊”单子,限制可以执行的动作类型;例如

data PhaseOneMonad a = PhaseOnePure a | PhaseOneUpdate BuildConfig Filename (Maybe XMLFilepath) a

instance Monad PhaseOneMonad where ...

这可能是你的意思吗?另请注意,PhaseOneUpdate不需要WorkConfig; 它只接受它感兴趣的构造函数参数。这是另一种常见的模式:你不能限制使用哪个构造函数,但你可以直接接受参数。

嗯……虽然还不确定。

于 2012-07-20T02:23:56.403 回答