人工生命実験のフレームワークを開発しています。各種が Agent クラスのインスタンスである限り、フレームワークは複数の種をサポートできます。各エージェントを AgentBox にラップして、基になる型を知らなくても読み書き、使用できるようにします。
これはうまく機能しますが、フレームワークのユーザーが作成しなければならない小さなボイラープレート関数が 1 つあります。これを回避する方法があるかどうか知りたいです。関数の型シグネチャで型変数が言及されていないため、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