获取构造函数的名称

时间:2020-01-28 13:11:35

标签: haskell generics template-haskell

我正在尝试找到一种方法来获取数据类型构造函数的名称为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作为参数,而不是构造函数:/

1 个答案:

答案 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