【发布时间】:2019-04-22 04:18:37
【问题描述】:
我想编写一个处理持久实体的简单框架。 这个想法是拥有一个实体类型类并提供通用的持久性操作,例如
storeEntity :: (Entity a) => a -> IO ()
retrieveEntity :: (Entity a) => Integer -> IO a
publishEntity :: (Entity a) => a -> IO ()
实际数据类型是该实体类型类的实例。
即使持久化操作是通用的并且不需要任何有关具体数据类型的信息,您也必须在调用站点提供类型注释以使 GHC 满意,例如:
main = do
let user1 = User 1 "Thomas" "Meier" "tm@meier.com"
storeEntity user1
user2 <- retrieveEntity 1 :: IO User -- how to avoid this type annotation?
publishEntity user2
有什么方法可以避免这种调用站点注释?
如果编译器可以从使用的上下文中推断出实际类型,我知道我不需要这些注释。因此,例如以下代码可以正常工作:
main = do
let user1 = User 1 "Thomas" "Meier" "tm@meier.com"
storeEntity user1
user2 <- retrieveEntity 1
if user1 == user2
then publishEntity user2
else fail "retrieve of data failed"
但我希望能够像这样链接多态动作:
main = do
let user1 = User 1 "Heinz" "Meier" "hm@meier.com"
storeEntity user1
-- unfortunately the next line does not compile
retrieveEntity 1 >>= publishEntity
-- but with a type annotation it works:
(retrieveEntity 1 :: IO User) >>= publishEntity
但是这里有一个类型注释破坏了多态的优雅......
为了完整起见,我已经包含了完整的源代码:
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
module Example where
import GHC.Generics
import Data.Aeson
-- | Entity type class
class (ToJSON e, FromJSON e, Eq e, Show e) => Entity e where
getId :: e -> Integer
-- | a user entity
data User = User {
userId :: Integer
, firstName :: String
, lastName :: String
, email :: String
} deriving (Show, Eq, Generic, ToJSON, FromJSON)
instance Entity User where
getId = userId
-- | load persistent entity of type a and identified by id
retrieveEntity :: (Entity a) => Integer -> IO a
retrieveEntity id = do
-- compute file path based on id
let jsonFileName = getPath id
-- parse entity from JSON file
eitherEntity <- eitherDecodeFileStrict jsonFileName
case eitherEntity of
Left msg -> fail msg
Right e -> return e
-- | store persistent entity of type a to a json file
storeEntity :: (Entity a) => a -> IO ()
storeEntity entity = do
-- compute file path based on entity id
let jsonFileName = getPath (getId entity)
-- serialize entity as JSON and write to file
encodeFile jsonFileName entity
-- | compute path of data file based on id
getPath :: Integer -> String
getPath id = ".stack-work/" ++ show id ++ ".json"
publishEntity :: (Entity a) => a -> IO ()
publishEntity = print
main = do
let user1 = User 1 "Thomas" "Meier" "tm@meier.com"
storeEntity user1
user2 <- retrieveEntity 1 :: IO User
print user2
【问题讨论】:
-
多种方式;将其传递给仅适用于
Users 的函数,例如,或对其进行模式匹配。我认为这在实际使用中的问题要比在像这个唯一消费者也是多态的玩具示例中要小得多。 -
感谢丹尼尔的快速回复。我同意在很多情况下消费者可能知道实际的数据类型。但我希望能够链接多态动作。在这种情况下,消费者也是多态的。有什么想法吗?
-
@ThomasMahler 你会考虑像
retrieveEntity 1 >>= publishEntity @User这样更好(使用TypeApplications扩展名)吗?您无法避免需要指定所需类型somewhere 的事实。在此示例中,如果单态函数publishEntity @User要接收适当的参数,编译器可以推断retrieveEntity必须返回IO User。
标签: haskell typeclass parametric-polymorphism