我正在尝试找到一种方法来获取数据类型构造函数的名称为String
data Test
= Foo
{ a :: Int
, b :: Int
}
| Bar
{ a :: Int
}
| Lol
| Lel String
我正在搜索name :: Constructor Test -> String
形式的内容,可以像这样使用
name Lol -- "Lol"
name Foo -- "Foo"
name Lel -- "Lel"
我最能实现的目标是:
module Main where
import Data.Typeable
import Data.Data
data Test
= Foo
{ a :: Int
, b :: Int
}
| Bar
{ a :: Int
}
| Lol
| Lel String
deriving (Show, Data, Typeable)
main :: IO ()
main = do
print $ toConstr Lol
print $ toConstr $ Bar undefined
print $ toConstr $ Foo undefined undefined
但是toConstr
除外object作为参数,而不是构造函数:/
答案 0 :(得分:2)
请注意,构造函数本身具有类型:
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"
但是,既然您拥有该功能,我想您会发现在实际程序中很难使用它。
无论如何,完整的代码如下。请注意,现代版本的GHC不需要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