首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用Haskell中的记录进行动态字段查找

用Haskell中的记录进行动态字段查找
EN

Stack Overflow用户
提问于 2018-01-18 15:34:13
回答 2查看 1.1K关注 0票数 5

我想知道是否有可能在Haskell中以特定的名称获得记录的所有字段。例如

代码语言:javascript
复制
data Record = Record {
    field       :: String
    field2_ids  :: Maybe [Int]
    field3_ids  :: Maybe [Int]
}

在本例中,我希望得到以"ids“结尾的字段列表。我不知道他们的名字。我只知道它们以“it”结尾,我需要的是字段名和它包含的值。所以我想这是一张地图列表

代码语言:javascript
复制
[{field2_ids = Maybe [Int]}, {fields3_ids = Maybe [Int]}...]

甚至是一个元组列表

代码语言:javascript
复制
[("field2_ids", Maybe [Int])...]

顺便说一句,在我的例子中,我正在提取的字段总是具有Maybe [Int]的类型。

这个是可能的吗?我怀疑这不可能与香草记录语法,但这可能是什么东西,可以实现的镜头?

更新

我明白我的问题在我真正想要做的事情上引起了一些混乱。所以我会解释

我使用的是微服务模式和服务。每个服务都绑定到一个数据模型。例如,博客服务将包含单个博客模型。但是博客服务可以有各种各样的关系。例如,它可以与类别服务有关系。它还可以与标记服务有关联。由于有可能与另一个服务建立多个关系,所以我有Maybe [Int]的类型,因为我可以用Just [Int]Nothing发布博客,根本就没有关系。每个服务通过在关系表中注册它们来处理其关系。

因此,要创建一个新的博客帖子,我需要一个像这样的数据结构在仆人中

代码语言:javascript
复制
data BlogPostRequest = BlogPostRequest {
    title :: String,
    published :: DateTime,
    public :: Bool,
    category_ids :: Maybe [Int],
    tag_ids :: Maybe [Int]
}

端点将接受与博客模型相关的所有字段,并将其存储为一个新的Blog实例。然后,如果存在于category_ids和tag_ids中,则它将获取所有关系,并将其存储在关系表中。

我唯一关心的是,使用传统的记录语法,如果我有多个关系,代码会变得非常臃肿。服务是从配置文件生成的。所以是的,我从一开始就知道所有字段的名称。很抱歉我之前关于这件事的陈述让人很困惑。我的观点是,如果我知道字段的名称以_ids结尾,就可以将字段从记录中删除,我就可以减少大量的代码。

这将是普通的记录语法方法。假设storeRelation是一个方法,它接受一个String和一个Maybe [Int],并相应地处理这个关系的存储。

代码语言:javascript
复制
createNewBlogPost post = 
    storeRelation "category" (category_ids post)
    storeRelation "tag"      (tag_ids post)
    -- continue with rest of relations

这种方法最终可能不会那么糟糕。我只想为每个关系添加一个新的行。我只是想知道是否有一种直接的方式从记录中提取字段,这样我就可以拥有这样的函数

代码语言:javascript
复制
createNewBlogPost post = 
    storRelation $ extractRelations post

其中storeRelation现在接受一个元组列表,而extractRelations是一个提取以_ids结尾的字段的函数

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-01-18 23:22:25

考虑到您确实知道所有的字段名,而且它们都是相同的类型,那么只编写每个字段名一次就应该是相当少的工作,而且比编写一个适用于任何数据类型的大型通用模板Haskell解决方案要简单得多。

一个简单的例子:

代码语言:javascript
复制
idGetters :: [(String, Record -> Maybe [Int])]
idGetters = [("field2_ids", field2_ids), 
             ("field3_ids", field3_ids)]

ids :: Record -> [(String, Maybe [Int])]
ids r = fmap (fmap ($ r)) idGetters

它看起来有点难看,但这只是处理您预先假定的数据结构的最佳方法。

票数 3
EN

Stack Overflow用户

发布于 2018-01-19 19:45:32

我已经想出了一个使用GHC.Generics的复杂解决方案,这个解决方案似乎很有效。我在某种程度上概括了这个问题,编写了一个具有以下类型签名的函数:

代码语言:javascript
复制
fieldsDict :: (Generic a, GFieldsDict (Rep a) t) => a -> M.Map String t

具体来说,这需要一个a类型的值,它很大程度上是一个记录,它生成一个从字段名到t类型值的映射。忽略具有t以外类型的字段。

使用实例

首先是它所做的事情的一个例子。以下是您问题中的Record类型,以及一个示例值:

代码语言:javascript
复制
data Record = Record
  { field :: String
  , field2_ids :: Maybe [Int]
  , field3_ids :: Maybe [Int]
  } deriving (Generic, Show)

exampleRecord :: Record
exampleRecord = Record
  { field = "a"
  , field2_ids = Just [1, 2]
  , field3_ids = Just [3, 4] }

使用fieldsDict,可以获得Maybe [Int]类型的所有字段

代码语言:javascript
复制
ghci> fields exampleRecord :: M.Map String (Maybe [Int])
fromList [("field2_ids",Just [1,2]),("field3_ids",Just [3,4])]

要将结果限制在以_ids结尾的字段中,只需通过键过滤得到的映射,这是留给读者的练习。

实现

我会先说:实现得不太好。GHC.Generics不是我最喜欢的API,但至少它是可能的。在开始之前,我们需要一些GHC扩展:

代码语言:javascript
复制
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

我们还需要一些进口品:

代码语言:javascript
复制
import qualified Data.Map as M

import Data.Proxy
import GHC.Generics
import GHC.TypeLits

使这项工作最困难的部分是能够分析哪些字段是所需的类型。为此,我们需要一种“强制转换”GHC.Generics类型表示的方法,我们将使用一个单独的类来表示该表示:

代码语言:javascript
复制
class GCast f g where
  gCast :: f p -> Maybe (g p)

不幸的是,实现这一点很困难,因为我们需要在f上执行案例分析,以确定它是否与g的类型相同,如果不是,则需要产品Nothing。如果我们把这个想法翻译成类型化,我们就会得到重叠的实例。为了缓解这个问题,我们可以使用一个使用封闭类型家族的技巧:

代码语言:javascript
复制
type family TyEq f g where
  TyEq f f = 'True
  TyEq f g = 'False

instance (TyEq f g ~ flag, GCast' flag f g) => GCast f g where
  gCast = gCast' (Proxy :: Proxy flag)

class GCast' (flag :: Bool) f g where
  gCast' :: Proxy flag -> f p -> Maybe (g p)

instance GCast' 'True f f where
  gCast' _ = Just

instance GCast' 'False f g where
  gCast' _ _ = Nothing

请注意,这意味着GCast类只有一个实例,但是保持gCast作为类方法而不是自由浮动函数仍然很有用,以便以后可以使用GCast作为约束。

接下来,我们将编写一个类,它将实际分析记录类型的GHC.Generics表示:

代码语言:javascript
复制
class GFieldsDict f t where
  gFieldsDict :: f p -> M.Map String t

这允许我们从前面定义我们的fieldsDict函数:

代码语言:javascript
复制
fieldsDict :: (Generic a, GFieldsDict (Rep a) t) => a -> M.Map String t
fieldsDict = gFieldsDict . from

现在,我们只需要实现GFieldsDict的实例。要通知这些实例,我们可以查看Rep Record的扩展表示

代码语言:javascript
复制
ghci> :kind! Rep Record
Rep Record :: GHC.Types.* -> *
= D1
    ('MetaData "Record" "FieldsDict" "main" 'False)
    (C1
       ('MetaCons "Record" 'PrefixI 'True)
       (S1
          ('MetaSel
             ('Just "field")
             'NoSourceUnpackedness
             'NoSourceStrictness
             'DecidedLazy)
          (Rec0 String)
        :*: (S1
               ('MetaSel
                  ('Just "field2_ids")
                  'NoSourceUnpackedness
                  'NoSourceStrictness
                  'DecidedLazy)
               (Rec0 (Maybe [Int]))
             :*: S1
                   ('MetaSel
                      ('Just "field3_ids")
                      'NoSourceUnpackedness
                      'NoSourceStrictness
                      'DecidedLazy)
                   (Rec0 (Maybe [Int])))))

从这个角度来看,我们需要实例来深入研究D1C1:*:,然后才能获得实际字段。这些实例编写起来相当简单,因为它们只是服从于类型表示的更多嵌套部分:

代码语言:javascript
复制
instance GFieldsDict f t => GFieldsDict (D1 md (C1 mc f)) t where
  gFieldsDict (M1 (M1 rep)) = gFieldsDict rep

instance (GFieldsDict f t, GFieldsDict g t) => GFieldsDict (f :*: g) t where
  gFieldsDict (f :*: g) = M.union (gFieldsDict f) (gFieldsDict g)

实际的功能将在S1上的实例中进行,因为每个S1类型对应于各个记录字段。这个实例将使用我们以前的GCast类:

代码语言:javascript
复制
instance (KnownSymbol name, GCast f (Rec0 t)) => GFieldsDict (S1 ('MetaSel ('Just name) su ss ds) f) t where
  gFieldsDict (M1 (rep :: f p)) = case gCast rep :: Maybe (Rec0 t p) of
    Just (K1 v) -> M.singleton (symbolVal (Proxy :: Proxy name)) v
    Nothing -> M.empty

…仅此而已。这种复杂性值得吗?也许不会,除非您可以在这里的库中隐藏它,但这说明了这是可能的。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48324939

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档