【发布时间】:2020-05-27 09:46:50
【问题描述】:
在我的 Haskell 程序中,我需要以各种方式从 API 服务加载记录。有一个loadSmall :: IO Small 操作仅加载可用字段中的一些字段。 loadBig :: IO Big 操作会加载更多字段。未来可能需要更多“级别”的加载。
为简单起见,我们假设Big 将始终包含Small 所做的一切。
我希望函数能够以统一的方式访问该类型的这两个“版本”。我已经阅读了lenses,并认为我可能会尝试在这里使用它们,但如果有更简单的方法,我根本不会使用lens。
这是我想出的:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
class HasSmall a where
name :: Lens' a Text
class HasSmall a => HasBig a where
email :: Lens' a Text
data Big = Big
{ _bigName :: Text
, _bigEmail :: Text
-- ...possibly many more fields
}
deriving Show
makeLenses ''Big
instance HasSmall Big where
name = bigName
instance HasBig Big where
email = bigEmail
data Small = Small
{ _smallName :: Text
-- ...probably at least a few fields more
}
deriving Show
makeLenses ''Small
instance HasSmall Small where
name = smallName
-- Function that uses name
useName :: HasSmall a => a -> Text
useName s = "Hello " <> (s ^. name)
这看起来确实像很多样板,因为现在每个新字段都必须写在至少三个地方。
有没有更有效的方法来做到这一点?
【问题讨论】:
-
lens 的 TH 机制包括
makeFields,它生成不同方向的抽象类(即每个字段一个类)。如果您只需要跨表示的统一字段访问,这可能就足够了。 -
谢谢,这看起来确实是个不错的选择。我有点惊讶 makeFields 似乎没有验证字段是否具有相同的类型。我可以将 bigName 更改为 Int 并且程序仍然可以编译。如果 HasName 可以将名称的类型固定为 Text 可能会更好一些,这样所有消费函数的类型签名就不需要指出该名称 :: Text。我没有立即在镜头文档中看到这样的选项。
标签: haskell haskell-lens