Полагаю, стандартным приемом является использование Typeable
, в частности typeOf :: Typeable a => a -> TypeRep
.К сожалению, у нас нет a
, лежащего вокруг, чтобы вызывать это до тех пор, пока мы не прочитаем файл, что мы не можем сделать, пока у нас не будет правильного имени файла, что мы не можем сделать, пока мы не вызовем typeOf
, что мы не можем сделать до тех пор, пока не прочтем файл ...
... или можем?
{-# 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
Или есть менее изнурительные варианты,например, используя typeRep :: Typeable a => proxy a -> TypeRep
.Затем мы можем использовать 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