首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >避免不属于Haskell类的样板文件

避免不属于Haskell类的样板文件
EN

Stack Overflow用户
提问于 2012-09-27 20:10:09
回答 1查看 202关注 0票数 1

我正在开发一个人工生命实验的框架。该框架可以支持多个物种,只要每个物种都是Agent类的一个实例。我将每个代理包装在一个AgentBox中,这样我就可以在不知道底层类型的情况下读取、写入和使用它们。

这工作得很好,但是框架的用户必须编写一个小的样板函数。我很想知道有没有办法避免这种情况。我不能在Agent类中提供该函数的默认实现,因为函数的类型签名没有提到类型变量。我可以接受样板文件,但我很好奇是否有更好的方法。

这是我所说的最小的工作示例。最后的getRock函数是我希望避免强迫用户编写的函数。类Agent的每个实例都需要提供一个函数来读取代理并将其包装在一个盒子中,并且实现总是看起来与getRock完全一样。

代码语言:javascript
复制
{-# 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
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2012-09-27 21:30:54

您可以编写一个函数来为任何给定类型的a创建ReaderSpec,就像这样:

代码语言:javascript
复制
-- Create a 'ReaderSpec' that deserializes objects of type 'a'
mkReaderSpec :: (DS.Serialize a, Agent a) => String -> ReaderSpec

由于a没有出现在参数或返回类型中,因此有必要将该类型的代理作为额外的参数进行传递。通常,这是通过传递未定义的值来完成的。通过调用asTypeOf强制表达式具有a类型。

代码语言:javascript
复制
-- 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来选择一个类型和相关的类实例。

代码语言:javascript
复制
readerSpec = mkReaderSpec "Rock" (undefined :: Rock)
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12621063

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档