Получить имя конструктора - PullRequest
2 голосов
/ 28 января 2020

Я пытаюсь найти способ получить имя моего конструктора типа данных как 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 исключает объект в качестве аргумента вместо конструктора: /

1 Ответ

2 голосов
/ 28 января 2020

Обратите внимание, что сами конструкторы имеют типы:

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
...