我使用存在类型作为包装器。在我的代码中我知道封闭类型的一点,我想用它来做一些特定于封闭类型的东西。这是我能得到的最接近的:
{-# 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.
我怎样才能做到这一点?提前感谢您的任何建议。
答案 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 | ....
但是数据比实际类型更好,不要让它隐藏起来。
避免存在类型。不是拥有类型类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
}
避免存在类型。而不是为代理提供接口的类型类,而是具有由必要位组成的数据类型:
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
++ ...
避免存在类型。不是拥有存在的代理,而是具有联合类型的代理
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 ++ ....
避免存在类型。不是拥有存在的代理,而是分离出通用的代理代码,并且拥有包含此
的联合类型的代理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 ....),
...]
拒绝放弃存在类型,选择放弃静态类型的快乐轻松,并使用Dynamic
或Typable
或其他不支持的内容来恢复某些类型信息。
答案 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"