简体   繁体   English

在Haskell中如何进行类型反射

[英]How to have type reflection in Haskell

I've written a simple Yesod Rest Server that persists entities in JSON files. 我编写了一个简单的Yesod Rest Server,该服务器将实体持久存储在JSON文件中。 Entities are stored on disk in files named data/type.id.json. 实体存储在磁盘上名为data / type.id.json的文件中。 For instance retrieveCustomer "1234" should load data from file data/Customer.1234.json. 例如,retrieveCustomer“ 1234”应该从文件data / Customer.1234.json加载数据。

I'm using a polymorphic function retrieveEntity that can retrieve instances of any data type that instantiate the FromJSON typeclass. 我正在使用一个多态函数retrieveEntity,该函数可以检索实例化FromJSON类型类的任何数据类型的实例。 (This part works nicely) (这部分效果很好)

But at the moment I have to fill in the type name hardcoded in type-specific functions like retrieveCustomer. 但是目前,我必须填写在类型特定的函数(如retrieveCustomer)中硬编码的类型名称。

How do I manage to compute the type name dynamically in the generic retrieveEntity? 如何在通用的retrieveEntity中动态地计算类型名称? I think I'm basically looking for a Haskell type reflection mechanism which I did not come across so far? 我想我基本上是在寻找到目前为止尚未遇到的Haskell类型反射机制?

-- | retrieve a Customer by id
retrieveCustomer :: Text -> IO Customer
retrieveCustomer id = do
    retrieveEntity "Customer" id :: IO Customer

-- | load a persistent entity of type t and identified by id from the backend
retrieveEntity :: (FromJSON a) => String -> Text -> IO a
retrieveEntity t id = do
    let jsonFileName = getPath t id ".json"
    parseFromJsonFile jsonFileName :: FromJSON a => IO a

-- | compute path of data file
getPath :: String -> Text -> String -> String
getPath t id ex = "data/" ++ t ++ "." ++ unpack id ++ ex

-- | read from file fileName and then parse the contents as a FromJSON instance.
parseFromJsonFile :: FromJSON a => FilePath -> IO a
parseFromJsonFile fileName = do
    contentBytes <- B.readFile fileName
    case eitherDecode contentBytes of
        Left msg -> fail msg
        Right x  -> return x

I guess the standard trick is to use Typeable , specifically typeOf :: Typeable a => a -> TypeRep . 我猜标准技巧是使用Typeable ,特别是typeOf :: Typeable a => a -> TypeRep Unfortunately, we don't have an a lying around to call this on until after we've read the file, which we can't do until we have the right filename, which we can't do until we call typeOf , which we can't do until after we've read the file... 不幸的是,我们没有一个a躺在附近就调用这个直到我们读取的文件,这是我们不能做后,直到我们有正确的文件名,这是我们不能做,直到我们叫typeOf ,我们读取文件后才能执行...

...or can we? ...或者我们可以吗?

{-# LANGUAGE RecursiveDo #-}
import Data.Aeson
import Data.Text
import Data.Typeable
import qualified Data.ByteString.Lazy as B

retrieveEntity :: (FromJSON a, Typeable a) => Text -> IO a
retrieveEntity id = mdo
    let jsonFileName = getPath (typeOf result) id ".json"
    result <- parseFromJsonFile jsonFileName
    return result

getPath :: TypeRep -> Text -> String -> String
getPath tr id ex = "data/" ++ show tr ++ "." ++ unpack id ++ ex

parseFromJsonFile :: FromJSON a => FilePath -> IO a
parseFromJsonFile fileName = do
    contentBytes <- B.readFile fileName
    case eitherDecode contentBytes of
        Left msg -> fail msg
        Right x  -> return x

Or there are less mind-bending options, such as using typeRep :: Typeable a => proxy a -> TypeRep . 或者没有那么多弯腰的选择,例如使用typeRep :: Typeable a => proxy a -> TypeRep Then we can use ScopedTypeVariables to bring the appropriate type into scope. 然后,我们可以使用ScopedTypeVariables将适当的类型带入范围。

{-# LANGUAGE ScopedTypeVariables #-}
import Data.Aeson
import Data.Text
import Data.Typeable
import qualified Data.ByteString.Lazy as B

-- don't forget the forall, it's a STV requirement
retrieveEntity :: forall a. (FromJSON a, Typeable a) => Text -> IO a
retrieveEntity id = do
    let jsonFileName = getPath (typeRep ([] :: [a])) id ".json"
    result <- parseFromJsonFile jsonFileName
    return result

getPath :: TypeRep -> Text -> String -> String
getPath tr id ex = "data/" ++ show tr ++ "." ++ unpack id ++ ex

parseFromJsonFile :: FromJSON a => FilePath -> IO a
parseFromJsonFile fileName = do
    contentBytes <- B.readFile fileName
    case eitherDecode contentBytes of
        Left msg -> fail msg
        Right x  -> return x

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM