Haskell Reflection : 레코드에 필드가 있습니까?

Aug 25 2020

GHC Generics 도구를 사용하면 생성자 이름을 검사 할 수 있지만 필드 이름은 어떻습니까?

데이터 유형이 있다고 가정합니다.

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

그리고 다음과 같은 데이터가 있습니다.

aFoo :: Foo

다음과 같이 작성할 수 있습니다.

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

oo내가 사용하는 것이 안전하다는 것을 알고있는 생성자에 의해 접근 자를 보호합니다.

Generics를 사용하여 이것을 작성하는 방법이 있습니까? 비슷한 것이 fieldMay있습니까?

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

답변

7 HTNW Aug 25 2020 at 08:57

예, 가능합니다. 필드 이름은 형식 생성자 ( 인수) 의 Repas 인수에 기록됩니다 . Haddock은 글을 쓰고 예제 기반 클래스는 그 안에있는 정보를 무시하지만 이제 우리는 그것을 필요로합니다.M1MetaM1Generic

그래서 우리는 우리가 무엇을 다루고 있는지 알고 있습니다.

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

그래서 우리는 기본적으로 S1s를 검색 하고 올바른 이름을 가진 것을 선택합니다. 단순성을 위해 올바른 유형이있는 항목 만 미리 살펴 보겠습니다.

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

그리고 그게

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

따다!

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"