Обратите внимание, что сами конструкторы имеют типы:
Foo :: Int -> Int -> Test
Bar :: Int -> Test
Lol :: Test
Lel :: Strinng -> Test
, поэтому вы запрашиваете функцию name
, которая может принимать конструктор, тип которого соответствует любому из этих "шаблонов", для создания String
. Если вы записали сигнатуру типа для name
, она должна выглядеть примерно так:
name :: (a1 -> a2 -> ... -> an -> Test) -> String
или, если мы хотим использовать ее с любым объектом, а не только с Test
, что-то вроде:
name :: (a1 -> a2 -> ... -> an -> finalObject) -> String
, где число типов a
зависит от арности конструктора.
Нет простого способа написания такой функции в Haskell. На самом деле это невозможно в «простой» Haskell. Однако с некоторыми расширениями это можно сделать с помощью некоторой хитрости класса типов.
Необходимые расширения:
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
Идея состоит в том, чтобы ввести класс типов для функции name
:
class Name a where
name :: a -> String
и затем представить экземпляр, который обрабатывает случай, когда a
все еще нужны аргументы, предоставив undefined
для уменьшения количества аргументов на единицу:
instance Name (r -> a) where
name f = name (f undefined)
Этот экземпляр будет использоваться рекурсивно. Когда мы вызываем name Foo
, оно будет использовано для уменьшения этого значения до name (Foo undefined)
, а затем снова использовано для его уменьшения до name (Foo undefined undefined)
. Так как этот последний объект не соответствует шаблону r -> a
, мы будем готовы использовать экземпляр по умолчанию:
instance Name a where
name = show . toConstr
Этот код не будет работать как есть. Нам нужно добавить некоторые ограничения в соответствующие места и использовать прагму OVERLAPPING
для обработки этих перекрывающихся экземпляров, но окончательное определение класса типа и его экземпляров:
class Name a where
name :: a -> String
instance {-# OVERLAPPING #-} Name a => Name (r -> a) where
name f = name (f undefined)
instance (Data a) => Name a where
name = show . toConstr
Это прекрасно работает:
λ> name Foo
"Foo"
λ> name Bar
"Bar"
λ> name Lol
"Lol"
λ> name Lel
"Lel"
Однако теперь, когда у вас есть эта функция, я думаю, вы обнаружите, что ее невероятно сложно использовать в реальной программе.
В любом случае, полный код следует , Обратите внимание, что современные версии GH C не нуждаются в deriving Typeable
, поэтому вы можете не указывать их.
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
module Constructor where
import Data.Data
data Test
= Foo
{ a :: Int
, b :: Int
}
| Bar
{ a :: Int
}
| Lol
| Lel String
deriving (Show, Data)
class Name a where
name :: a -> String
instance {-# OVERLAPPING #-} Name a => Name (r -> a) where
name f = name (f undefined)
instance (Data a) => Name a where
name = show . toConstr
main = do
print $ name Foo
print $ name Bar
print $ name Lol
print $ name Lel