Подтвердить что ты не робот

Как читать метаданные типа во время выполнения?

Я бы хотел написать программу, которая выводит некоторые метаданные типа Haskell. Хотя я знаю, что это неверный код, идея выглядит примерно так:

data Person = Person { name :: String, age :: Int }

metadata :: Type -> String
metadata t = ???

metadata Person -- returns "Person (name,age)"

Важным ограничением является то, что у меня нет экземпляра Person, просто типа.

Я начал изучать Generics и Typeable/Data, но без экземпляра я не уверен, что они будут делать то, что мне нужно. Может ли кто-нибудь указать мне в правильном направлении?

4b9b3361

Ответ 1

Отражение в Haskell работает с использованием класса Typeable, который определен в Data.Typeable и включает в себя метод typeOf * для получения представление времени выполнения типа.

ghci> :m +Data.Typeable
ghci> :t typeOf 'a'
typeOf 'a' :: TypeRep
ghci> typeOf 'a'  -- We could use any value of type Char and get the same result
Char  -- the `Show` instance of `TypeRep` just returns the name of the type

Если вы хотите, чтобы Typeable работал для ваших собственных типов, вы можете заставить компилятор создать экземпляр для вас с расширением DeriveDataTypeable.

{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
data Person = Person { name :: String, age :: Int } deriving Typeable

Вы также можете написать свой собственный экземпляр, но на самом деле у него нет времени. По-видимому, вы не можете увидеть комментарии

Теперь вы можете использовать typeOf, чтобы захватить представление времени выполнения вашего типа. Мы можем запросить информацию о конструкторе типа (сокращенно TyCon) и его аргументах типа:

-- (undefined :: Person) stands for "some value of type Person".
-- If you have a real Person you can use that too.
-- typeOf does not use the value, only the type
-- (which is known at compile-time; typeOf is dispatched using the normal instance selection rules)
ghci> typeOf (undefined :: Person)
Person
ghci> tyConName $ typeRepTyCon $ typeOf (undefined :: Person)
"Person"
ghci> tyConModule $ typeRepTyCon $ typeOf (undefined :: Person)
"Main"

Data.Typeable также обеспечивает операцию литья под типом, которая позволяет вам входить в тип времени выполнения, как оператор С# as.

f :: Typeable a => a -> String
f x = case (cast x :: Maybe Int) of
           Just i -> "I can treat i as an int in this branch " ++ show (i * i)
           Nothing -> case (cast x :: Maybe Bool) of
                           Just b -> "I can treat b as a bool in this branch " ++ if b then "yes" else "no"
                           Nothing -> "x was of some type other than Int or Bool"

ghci> f True
"I can treat b as a bool in this branch yes"
ghci> f (3 :: Int)
"I can treat i as an int in this branch 9"

Кстати, более удобным способом написать f является использование GADT, перечисляющего набор типов, которые вы ожидаете от своей функции. Это позволяет нам потерять Maybe (f никогда не сработает!), Лучше документирует наши предположения и дает компиляцию обратной связи, когда нам нужно изменить набор допустимых типов аргументов для f. (Вы можете написать класс, чтобы сделать Admissible неявным, если хотите.)

data Admissible a where
    AdInt :: Admissible Int
    AdBool :: Admissible Bool
f :: Admissible a -> a -> String
f AdInt i = "I can treat i as an int in this branch " ++ show (i * i)
f AdBool b = "I can treat b as a bool in this branch " ++ if b then "yes" else "no"

В действительности я, вероятно, не сделал бы ни одного из них - я бы просто вложил f в класс и определял экземпляры для Int и Bool.


Если вам нужна информация о времени выполнения правой части определения типа, вам нужно использовать развлекательно-названный Data.Data, который определяет подкласс Typeable, называемый Data. ** GHC может вывести Data для вас тоже с тем же расширением:

{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
import Data.Data
data Person = Person { name :: String, age :: Int } deriving (Typeable, Data)

Теперь мы можем захватить представление во времени во времени значений типа, а не только самого типа:

ghci> dataTypeOf (undefined :: Person)
DataType {tycon = "Main.Person", datarep = AlgRep [Person]}
ghci> dataTypeConstrs $ dataTypeOf (undefined :: Person)
[Person]  -- Person only defines one constructor, called Person
ghci> constrFields $ head $ dataTypeConstrs $ dataTypeOf (undefined :: Person)
["name","age"]

Data.Data - API для общего программирования; если вы когда-нибудь слышали, как люди говорили о "Scrap Your Boilerplate" , это (наряду с Data.Generics, который основывается на Data.Data), что они означают. Например, вы можете написать функцию, которая преобразует типы записей в JSON, используя отражение в полях типа.

toJSON :: Data a => a -> String
-- Implementation omitted because it is boring.
-- But you only have to write the boring code once,
-- and it'll be able to serialise any instance of `Data`.
-- It a good exercise to try to write this function yourself!

* В последних версиях GHC этот API несколько изменился. Обратитесь к документам.

** Да, полное имя этого класса Data.Data.Data.