1

人工生命実験のフレームワークを開発しています。各種が 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
4

1 に答える 1

5

ReaderSpec次のように、任意の type に対して sを作成する関数を作成できます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任意の型に対して s を作成できます。ユーザーは、 を渡して、型と関連するクラス インスタンスを選択しますundefined

readerSpec = mkReaderSpec "Rock" (undefined :: Rock)
于 2012-09-27T13:30:54.303 に答える