解构存在主义类型

时间:2012-10-11 14:52:16

标签: haskell pattern-matching existential-type

我使用存在类型作为包装器。在我的代码中我知道封闭类型的一点,我想用它来做一些特定于封闭类型的东西。这是我能得到的最接近的:

 {-# LANGUAGE ExistentialQuantification #-}

class Agent a where
  agentId :: a -> String
  speciesId :: a -> String
  -- plus other functions that all agents support

-- | A wrapper allowing my daemon to read and write agents of any species.
--   (Agents are stored in files that contain a tag so I know which function
--   to call to read the agent.)
data AgentBox = forall a. Agent a => AgentBox { unbox :: a }

instance Agent AgentBox where
  agentId (AgentBox a) = agentId a
  speciesId (AgentBox a) = speciesId a
  -- plus other functions that all agents support

bugTag :: String
bugTag = "Bug"

data Bug = Bug String

instance Agent Bug where
  agentId (Bug name) = name
  speciesId _ = bugTag

doSomethingWith :: AgentBox -> IO ()
doSomethingWith a = do
  if speciesId a == bugTag
    then do
      -- Now I know it's a bug, and I want to do something bug-specific
      doBugStuff2 a
      return ()
    else return ()

doBugStuff :: Bug -> IO ()
doBugStuff a = putStrLn $ agentId a ++ " does bug stuff"

doBugStuff2 AgentBox{unbox=a} = doBugStuff (a `asTypeOf` model) -- line 39
  where model = undefined :: Bug

我得到的错误是:

Amy30.hs:39:45:
    Could not deduce (a ~ Bug)
    from the context (Agent a)
      bound by a pattern with constructor
                 AgentBox :: forall a. Agent a => a -> AgentBox,
               in an equation for `doBugStuff2'
      at Amy30.hs:39:13-29
      `a' is a rigid type variable bound by
          a pattern with constructor
            AgentBox :: forall a. Agent a => a -> AgentBox,
          in an equation for `doBugStuff2'
          at Amy30.hs:39:13
    In the first argument of `asTypeOf', namely `a'
    In the first argument of `doBugStuff', namely
      `(a `asTypeOf` model)'
    In the expression: doBugStuff (a `asTypeOf` model)
Failed, modules loaded: none.

我怎样才能做到这一点?提前感谢您的任何建议。

3 个答案:

答案 0 :(得分:8)

使用Data.Dynamic

import Data.Dynamic

class Typeable a => Agent a where
  agentId :: a -> String
  -- no need for speciesId

fromAgentBox :: Agent a => AgentBox -> Maybe a
fromAgentBox (AgentBox inner) = fromDynamic (toDyn inner)

instance Agent Bug where
  agentId (Bug name) = name
  -- no need for speciesId

doSomethingWith :: AgentBox -> IO ()
doSomethingWith a = do
  case fromAgentBox a of
    Just bug -> do
      -- Now the compiler knows it's a bug, and I can do something bug-specific
      doBugStuff2 bug
      return ()
    Nothing -> return ()

或者,考虑在doSomethingWith类中声明Agent,也许使用默认定义。

class Agent a where
  agentId :: a -> String
  -- still don't need speciesId
  doSomethingWith :: a -> IO ()
  doSomethingWith _ = return ()

instance Agent Bug where
  agentId (Bug name) = name
  -- still don't need speciesId
  doSomethingWith bug = do
    -- Now the compiler knows it's a bug, and I can do something bug-specific
    doBugStuff2 bug
    return ()

最后,我应该指出,您的AgentBox类型是existential typeclass anti-pattern的一个示例,因此您应该忽略我上面写的内容并重新设计Agent类作为普通数据类型。

答案 1 :(得分:4)

  

Could not deduce (a ~ Bug)

我们可以,但编译器不能。
我们知道agentId 意味着是单射的,因此不同类型的两个实例具有相同的agentId字符串,但编译器无法推断出它。应用函数Agent a -> String会丢失关于a的任何类型信息,并且您没有太多因为它是存在性的。

问题1:存在数据类型会阻止编译器使用数据类型。这是你问题的核心。你决定要让它们成为不同的类型然后你决定要它们都是一种类型。

问题2:字符串不是类型,类型是。优于字符串是用户定义的类型,例如

data Species = Bug | Saurapod | ....

但是数据比实际类型更好,不要让它隐藏起来。

解决方案1:

避免存在类型。不是拥有类型类Agent,而是拥有记录类型data Agent,使所有代理均匀。

data Agent = Agent {
    agentId :: String,
    speciesId :: Species,
    -- ...other stuff agents need. 
    -- Species-specific data is an illusion; 
    -- make Agent widely useful, catering for the eventualities
    }

解决方案2:

避免存在类型。而不是为代理提供接口的类型类,而是具有由必要位组成的数据类型:

data Agent = Agent {
    agentId :: String,
    speciesId :: Species,
    -- ...other stuff _all_ agents need. 
    }

class IsAgent a where
  agent :: a -> Agent

现在你可以拥有

agents::[Agent]
agents = map agent bugs 
      ++ map agent saurapods 
      ++ ...

解决方案3:

避免存在类型。不是拥有存在的代理,而是具有联合类型的代理

class Agent a where
   -- all the stuff you want
instance Agent Bug where
   ...
instance Agent Saurapod where
   ...
data AnyAgent = ABug Bug | ASaurapod Saurapod | ... 
   -- ensure you have an agent instance for each type you need

instance Agent AnyAgent where
   -- much boilerplate code unwrapping and applying

agents :: [AnyAgent]
agents = map ABug bugs ++ map ASaurapod saurapods ++ ....

解决方案4:

避免存在类型。不是拥有存在的代理,而是分离出通用的代理代码,并且拥有包含此

的联合类型的代理
data Agent = Agent {
    agentId :: String,
    -- ...other stuff _all_ agents need. 
    }

data Bug = Bug --..... Bug-specific data
data Saurapod = Saurapod --... Saurapod-specific data

data AnyAgent = ABug Agent Bug | ASaurapod Agent Saurapod | ... 

agent :: AnyAgent -> Agent
agent (ABug a _) = a
agent (ASaurapod a _) = a
...

agents :: [AnyAgent]
agents = [ABug (Agent {agentId="007", ...}) (Bug ....),
          ASaurapod (Agent {agentId="Pat", ...}) (Saurapod ....),
          ...]

解决方案5

拒绝放弃存在类型,选择放弃静态类型的快乐轻松,并使用DynamicTypable或其他不支持的内容来恢复某些类型信息。

答案 2 :(得分:3)

你必须说服类型检查器,你的类型是Bug。 您可以通过将Data.Typeable.Typeable设置为Agent的超类,然后使用Data.Typeable.cast从存在类型向下转换为实际类型来实现此目的。

但在此之前,请考虑采用其他方式。这不是Haskellish,而是OO风格。

{-# LANGUAGE ExistentialQuantification, DeriveDataTypeable #-}
import Data.Typeable
import Data.Maybe

class Typeable a => Agent a where
  agentId :: a -> String
  speciesId :: a -> String

data AgentBox = forall a. Agent a => AgentBox { unbox :: a }
    deriving (Typeable)

instance Agent AgentBox where
  agentId (AgentBox a) = agentId a
  speciesId (AgentBox a) = speciesId a

bugTag :: String
bugTag = "Bug"

data Bug = Bug String
    deriving (Typeable)

instance Agent Bug where
  agentId (Bug name) = name
  speciesId _ = bugTag

doSomethingWith :: AgentBox -> IO ()
doSomethingWith a = do
  case cast a of
    Just bug -> doBugStuff bug
    Nothing -> return ()

doBugStuff :: Bug -> IO ()
doBugStuff a = putStrLn $ agentId a ++ " does bug stuff"