受到类型的挑战

时间:2012-07-20 00:32:49

标签: haskell types

问题:

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

3 个答案:

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

这不需要修改fMyType类型。

答案 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;它只需要它感兴趣的构造函数参数。这是另一种常见的模式:你不能约束使用哪个构造函数,但你可以直接获取参数。

嗯......但仍不确定。