Haskell 具有相同逻辑类型的不同字段的记录
在Haskell程序中,我需要以各种方式从API服务加载记录。有一个Haskell 具有相同逻辑类型的不同字段的记录,haskell,haskell-lens,Haskell,Haskell Lens,在Haskell程序中,我需要以各种方式从API服务加载记录。有一个loadSmall::IO Small操作,它只加载可用字段中的一些字段。一个loadBig::IO Big操作加载更多字段。也许将来需要更多的“级别”加载 为了简单起见,我们假设Big总是包含Small所做的一切 我希望函数能够以统一的方式访问该类型的这两个“版本”。我读过关于镜头的书,认为我可能会在这里尝试使用它们,但如果有更简单的方法,我根本不打算使用镜头 这就是我想到的: {-# LANGUAGE TemplateHas
loadSmall::IO Small
操作,它只加载可用字段中的一些字段。一个loadBig::IO Big
操作加载更多字段。也许将来需要更多的“级别”加载
为了简单起见,我们假设Big
总是包含Small
所做的一切
我希望函数能够以统一的方式访问该类型的这两个“版本”。我读过关于镜头的书,认为我可能会在这里尝试使用它们,但如果有更简单的方法,我根本不打算使用镜头
这就是我想到的:
{-# 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)
{-#语言模板haskell}
进口管制.镜头
a班在哪里
名称::Lens’a文本
类hassall=>HasBig a where
电子邮件::Lens’a文本
数据大=大
{u bigName::Text
,_bigmail::Text
--…可能还有更多的领域
}
衍生节目
使透镜变大
例如:大的在哪里
name=bigName
实例有大有大在哪里
电子邮件=大电子邮件
数据小=小
{u smallName::Text
--…可能至少还有几个领域
}
衍生节目
使透镜变小
实例:小哪里
name=smallName
--使用名称的函数
useName::Hassall a=>a->Text
useName s=“你好”(s^.name)
这看起来像是很多样板,因为现在每个新领域都必须至少在三个地方编写
有没有更有效的方法来实现这一点?如果
Big
应该包含Small
中包含的所有内容,那么可以将Small
设置为Big
字段:
{-# LANGUAGE RankNTypes #-}
module Main where
class HasSmall a where
accessSmall :: (Small -> b) -> (a -> b)
data Small = Small
{ name :: String
, address :: String
-- ...probably at least a few fields more
}
deriving Show
instance HasSmall Small where
accessSmall = id
data Big = Big
{ small :: Small
, email :: String
-- ...possibly many more fields
}
deriving Show
instance HasSmall Big where
accessSmall f = f . small
exampleSmall :: Small
exampleSmall = Small { name = "small name", address = "small address"}
exampleBig :: Big
exampleBig = Big { small = exampleSmall, email = "big email"}
printNameAndAddress :: HasSmall a => a -> IO ()
printNameAndAddress a = do
putStrLn $ accessSmall name a
putStrLn $ accessSmall address a
main :: IO ()
main = do
printNameAndAddress exampleBig
printNameAndAddress exampleSmall
这种方法不需要镜头,但也可以通过更改HasSmall
类轻松修改以使用镜头:
class HasSmall a where
accessSmall :: Lens' Small b -> Lens' a b
instance HasSmall Small where
accessSmall = id
instance HasSmall Big where
accessSmall = (.) small
GHCi> :info HasSmall
class HasSmall c where
small :: Lens' c Small
name :: Lens' c Text
{-# MINIMAL small #-}
-- Defined at Test.hs:16:1
instance HasSmall Small -- Defined at Test.hs:16:1
instance HasSmall Big -- Defined at Test.hs:27:10
GHCi> :set -XTypeApplications
GHCi> :t name @Big
name @Big :: Functor f => (Text -> f Text) -> Big -> f Big
就目前而言,最接近您需要的工具是makeClassy
:
data Small = Small
{ _name :: Text
-- ...probably at least a few fields more
}
deriving Show
makeClassy ''Small
data Big = Big
{ _bigSmall :: Small
, _bigEmail :: Text
-- ...possibly many more fields
}
deriving Show
makeClassy ''Big -- As far as this demo goes, not really necessary.
instance HasSmall Big where
small = bigSmall
这种方法要求您在Big
中有一个Small
字段,以便可以通过生成的hasmall
类来访问Small
中的字段:
class HasSmall a where
accessSmall :: Lens' Small b -> Lens' a b
instance HasSmall Small where
accessSmall = id
instance HasSmall Big where
accessSmall = (.) small
GHCi> :info HasSmall
class HasSmall c where
small :: Lens' c Small
name :: Lens' c Text
{-# MINIMAL small #-}
-- Defined at Test.hs:16:1
instance HasSmall Small -- Defined at Test.hs:16:1
instance HasSmall Big -- Defined at Test.hs:27:10
GHCi> :set -XTypeApplications
GHCi> :t name @Big
name @Big :: Functor f => (Text -> f Text) -> Big -> f Big
另一种方法是通过makeFields
对字段进行抽象:
data Small = Small
{ _smallName :: Text
-- ...probably at least a few fields more
}
deriving Show
makeFields ''Small
data Big = Big
{ _bigName :: Text
, _bigEmail :: Text
-- ...possibly many more fields
}
deriving Show
makeFields ''Big
在这个用例中,
makeFields
的一个潜在缺点是,正如您所注意到的,机器完全打开,可以为字段指定哪些类型。(相比之下,makeClassy
示例中的Small
的定义间接指定了任何名称
镜头都将具有文本
类型的目标。)镜头的第四个机制包括,它生成以不同方向抽象的类(即,每个字段一个类)。如果您只需要在表示中统一访问字段,那么这可能就足够了。谢谢,这看起来确实是一个不错的选择。我有点惊讶makeFields似乎没有验证字段是否具有相同的类型。我可以将bigName更改为Int,程序仍然可以编译。如果HasName可以将名称的类型固定为Text,那么可能会稍微好一些,这样所有消费函数的类型签名就不需要指出name::Text。我没有立即在镜头文档中看到这样的选项。