Избегайте шаблонов, которые не являются частью класса Haskell

Я разрабатываю рамки для экспериментов с искусственной жизнью. Каркас может поддерживать несколько видов, если каждый вид является экземпляром класса 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

1 ответ

Решение

Вы можете написать функцию для создания ReaderSpecs для любого данного типа 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)}

Теперь рамки могут сделать ReaderSpecs для любого данного типа. Пользователь выбирает тип и связанные экземпляры класса, передавая undefined,

readerSpec = mkReaderSpec "Rock" (undefined :: Rock)
Другие вопросы по тегам