获取构造函数的名称

Get name of the constructor

我正在尝试找到一种方法来获取我的数据类型构造函数的名称作为字符串

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 将对象作为参数而不是构造函数除外 :/

请注意,构造函数本身具有类型:

Foo :: Int -> Int -> Test
Bar :: Int -> Test
Lol :: Test
Lel :: Strinng -> Test

所以你要求一个函数name,它可以接受一个类型与这些"patterns"中的任何一个匹配的构造函数来生成String。如果您写下 name 的类型签名,它需要类似于:

name :: (a1 -> a2 -> ... -> an -> Test) -> String

或者,如果我们想将它用于任何对象,而不仅仅是 Test,例如:

name :: (a1 -> a2 -> ... -> an -> finalObject) -> String

其中 a 类型的数量取决于构造函数的数量。

在 Haskell 中没有直接的方法来编写这样的函数。其实在"plain"Haskell是不可能的。然而,通过一些扩展,它可以使用一些类型 class 技巧来完成。

所需的扩展是:

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}

想法是为name函数引入一个类型class:

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 pragma 来处理这些重叠的实例,但是类型 class 及其实例的最终定义是:

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