You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell如何实现类型反射?Yesod服务动态获取类型名

Solution: Dynamic Type Names with Typeable

You’re right that Haskell’s type reflection (via the Typeable type class) is the perfect solution here. This lets us extract the type name dynamically, eliminating the need to hardcode it in each specific retrieve function. Here’s how to adjust your code:

Step 1: Add Required Imports

First, import the Typeable utilities we’ll need to reflect on type names:

import Data.Typeable (Typeable, typeRep, typeRepTyCon, tyConName, Proxy(..))
import Data.Aeson (FromJSON, eitherDecode)
import qualified Data.ByteString.Lazy as B
import Data.Text (Text, unpack)

Step 2: Simplify Specific Retrieve Functions

Since we’ll get the type name dynamically, we no longer need to pass hardcoded strings like "Customer" to retrieveEntity:

-- | Retrieve a Customer by id
retrieveCustomer :: Text -> IO Customer
retrieveCustomer = retrieveEntity

This pattern works for any entity type—just define a one-line function that delegates to retrieveEntity.

Step 3: Modify retrieveEntity to Use Type Reflection

Update retrieveEntity to remove the explicit type string parameter. Instead, use Typeable to extract the type name of the target entity:

-- | Load a persistent entity of type t and identified by id from the backend
retrieveEntity :: (FromJSON a, Typeable a) => Text -> IO a
retrieveEntity entityId = do
  -- Extract the unqualified type name (e.g., "Customer" for the Customer type)
  let typeName = tyConName $ typeRepTyCon $ typeRep (Proxy :: Proxy a)
      jsonFileName = getPath typeName entityId ".json"
  parseFromJsonFile jsonFileName

Breakdown of the Type Reflection Logic:

  • typeRep (Proxy :: Proxy a): Gets a TypeRep value that represents the generic type a.
  • typeRepTyCon: Extracts the type constructor from the TypeRep (e.g., the constructor for the Customer type).
  • tyConName: Converts the type constructor to its unqualified string name (exactly what we need for the file path).

Step 4: Keep Existing Helper Functions

Your getPath and parseFromJsonFile functions work perfectly as-is—they just use the dynamically generated type name string:

-- | 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

Key Benefits:

  • No more hardcoding: You won’t have to update string literals if you rename a type or add new entities.
  • Full generality: Any type that implements both FromJSON and Typeable can use retrieveEntity without extra code.
  • Consistent naming: The file naming scheme (data/Type.id.json) is enforced automatically based on the actual type.

Edge Case Note:

If your types are defined in a module (e.g., MyApp.Models.Customer), tyConName still returns just "Customer" (the unqualified name), which matches your desired file naming convention. If you ever needed the fully qualified module name, you could use tyConModule, but that’s unnecessary here.

内容的提问来源于stack exchange,提问作者Thomas Mahler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:11:25