我正在开发人工生命实验的框架。框架 只要每个物种都是一个实例,就可以支持多个物种 代理类。我将每个Agent包装在AgentBox中,以便我可以读取,写入和 在不知道基础类型的情况下使用它们。
这很好用,但是用户有一个小的样板功能 框架必须写。我很想知道是否有办法 避免这个。我不能提供该函数的默认实现 Agent类,因为函数的类型签名没有提到 类型变量。我可以忍受样板,但我很好奇 如果有更好的方法。
以下是我所谈论的最低工作示例。 getRock 最后的功能是我想避免强迫我的用户使用的功能 写。类Agent的每个实例都需要提供一个函数 读取代理并将其包装在一个框中,实现将始终 看起来和getRock完全一样。
{-# LANGUAGE ExistentialQuantification, DeriveGeneric #-}
import qualified Data.Serialize as DS (Get, Serialize, get, put)
import Data.Serialize.Put (PutM)
import Data.List (find)
import Data.Maybe (fromJust, isNothing)
import GHC.Generics ( Generic )
class Agent a where
agentId :: a -> String
speciesId :: a -> String
-- other functions to be added
-- This wrapper allows me to use Agents without knowing their type.
data AgentBox = forall a. (DS.Serialize a, Agent a) => AgentBox a
-- Instructions for deserialising an agent
data ReaderSpec = ReaderSpec { tag :: String, getter :: DS.Get AgentBox }
-- Serialise an AgentBox by putting the species tag, then the agent.
putAgentBox :: AgentBox -> PutM ()
putAgentBox (AgentBox a) = do
DS.put $ speciesId a
DS.put a
-- Deserialise an agent by getting the species tag, looking up the getter
-- for that species of agent, and then getting the agent itself.
getAgentBox :: [ReaderSpec] -> DS.Get (Either String AgentBox)
getAgentBox xs = do
s <- DS.get :: DS.Get String
let a = find (\x -> tag x == s) xs
if isNothing a
then return $ Left $ "No getter found for " ++ s
else do
let d = (getter . fromJust) a
t <- d
return $ Right t
--
-- Everything above this line is provided by the framework.
-- The user of the framework would create their own instances of the class
-- Agent, by writing something like this:
--
data Rock = Rock String Int deriving (Show, Generic)
rockTag :: String
rockTag = "Rock"
readerSpec :: ReaderSpec
readerSpec = ReaderSpec rockTag getRock
instance Agent Rock where
agentId (Rock name _) = name
speciesId _ = rockTag
-- other functions to be added
instance DS.Serialize Rock
-- | Get the agent and wrap it in a box.
getRock :: DS.Get AgentBox
getRock = do
t <- DS.get :: DS.Get Rock
return $ AgentBox t
答案 0 :(得分:5)
您可以为任何给定类型ReaderSpec
编写一个函数来创建a
,几乎是这样的:
-- Create a 'ReaderSpec' that deserializes objects of type 'a'
mkReaderSpec :: (DS.Serialize a, Agent a) => String -> ReaderSpec
由于a
未出现在参数或返回类型中,因此必须将该类型的代理作为额外参数传递。通常通过传递未定义的值来完成。通过调用a
来强制表达式asTypeOf
。
-- Create a 'ReaderSpec' that deserializes objects of type 'a'
mkReaderSpec :: (DS.Serialize a, Agent a) => String -> a -> ReaderSpec
mkReaderSpec tag dummy = ReaderSpec tag getter
where
getter = do {t <- DS.get; return $ AgentBox (t `asTypeOf` dummy)}
现在,框架可以为任何给定类型生成ReaderSpec
个。用户通过传递undefined
来选择类型和关联的类实例。
readerSpec = mkReaderSpec "Rock" (undefined :: Rock)