简体   繁体   English

Haskell 反思:记录有字段吗?

[英]Haskell reflection: does record have field?

GHC Generics tools let you inspect constructor name, but what about field names? GHC 泛型工具可让您检查构造函数名称,但字段名称呢?

Suppose I have a data type假设我有一个数据类型

data Foo
  = F {f :: Int}
  | OO {oo :: String}
  | Foo {f :: Int, oo :: String}

And I have the following piece of data我有以下数据

aFoo :: Foo

I can write something like:我可以写这样的东西:

ooMay :: Foo -> Maybe String
ooMay f@(Foo {}) = Just (oo f)
ooMay f@(OO {}) = Just (oo f)
ooMay f@(F {}) = Nothing

guarding the accessor oo by the constructors which I know it is safe to use upon.通过我知道可以安全使用的构造函数保护访问器oo

Is there a way to write this using Generics?有没有办法用泛型来写这个? Does something like fieldMay exist?fieldMay这样的fieldMay存在吗?

ooMay :: Foo -> Maybe String
ooMay f = fieldMay "oo" f

Yes, this is doable.是的,这是可行的。 The field names are written into the Rep as arguments to the M1 type constructor (the Meta argument).字段名称作为M1类型构造函数的参数( Meta参数)写入Rep The Haddock glosses over M1 and the example Generic -based class ignores the information in it, but now we'll need it. Haddock 掩盖了M1 ,示例Generic基于类忽略了其中的信息,但现在我们需要它。

Just so we know what we're dealing with:只是为了让我们知道我们在处理什么:

ghci> :kind! Rep Foo
Rep Foo :: * -> *
= D1
    ('MetaData "Foo" "Ghci1" "interactive" 'False)
    (C1
       ('MetaCons "F" 'PrefixI 'True)
       (S1
          ('MetaSel
             ('Just "f") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy)
          (Rec0 Int))
     :+: (C1
            ('MetaCons "OO" 'PrefixI 'True)
            (S1
               ('MetaSel
                  ('Just "oo")
                  'NoSourceUnpackedness
                  'NoSourceStrictness
                  'DecidedLazy)
               (Rec0 String))
          :+: C1
                ('MetaCons "Foo" 'PrefixI 'True)
                (S1
                   ('MetaSel
                      ('Just "f") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy)
                   (Rec0 Int)
                 :*: S1
                       ('MetaSel
                          ('Just "oo")
                          'NoSourceUnpackedness
                          'NoSourceStrictness
                          'DecidedLazy)
                       (Rec0 String))))

So we basically just search for the S1 s and pick the one with the correct name.所以我们基本上只是搜索S1并选择具有正确名称的那个。 For simplicity reasons, let's only look at the ones that have the right type beforehand.为简单起见,让我们先看看那些具有正确类型的。

class GFieldMay rep a where
    gFieldMay :: String -> rep p -> Maybe a
-- fields of the right type might match the name
instance {-# OVERLAPS #-} Selector s => GFieldMay (M1 S s (K1 i a)) a where
    gFieldMay name m@(M1 (K1 x))
      | name == selName m = Just x
      | otherwise = Nothing
-- any other fields must be pruned (no deep search)
instance {-# OVERLAPPING #-} GFieldMay (M1 S s f) a where
    gFieldMay _ _ = Nothing
-- drill through any other metadata
instance {-# OVERLAPPABLE #-} GFieldMay f a => GFieldMay (M1 i m f) a where
    gFieldMay name (M1 x) = gFieldMay name x
-- search both sides of products
instance (GFieldMay l a, GFieldMay r a) => GFieldMay (l :*: r) a where
    gFieldMay name (l :*: r) = gFieldMay name l <|> gFieldMay name r
-- search the given side of sums
instance (GFieldMay l a, GFieldMay r a) => GFieldMay (l :+: r) a where
    gFieldMay name (L1 x) = gFieldMay name x
    gFieldMay name (R1 x) = gFieldMay name x

And that's that就是这样

fieldMay :: (Generic a, GFieldMay (Rep a) f) => String -> a -> Maybe f
fieldMay name = gFieldMay name . from

Ta-da!达达!

main = putStr $ unlines $
  [ show x ++ ": " ++ show (fieldMay "oo" x :: Maybe String)
  | x <- [F 42, OO "5", Foo 42 "5"]]
-- F {f = 42}: Nothing
-- OO {oo = "5"}: Just "5"
-- Foo {f = 42, oo = "5"}: Just "5"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM