Я не уверен, к чему вы клоните с помощью этой функции test
, но вы можете сделать что-то подобное, если хотите:
{-# LANGUAGE ScopedTypeVariables #-}
class NamedType a where
name :: a -> String
instance NamedType Int where
name _ = "Int"
instance NamedType Integer where
name _ = "Integer"
instance NamedType q => NamedType (Zq q) where
name _ = "Zq (" ++ name (undefined :: q) ++ ")"
Я бы не выполнял свои обязанности по переполнению стека, если бы не следил за этим ответом с предупреждением: то, что вы просите, очень и очень странно. Вы, вероятно, делаете что-то очень однотипно и будете сражаться с языком весь путь. Я настоятельно рекомендую, чтобы ваш следующий вопрос был гораздо более широким, чтобы мы могли помочь вам найти более идиоматическое решение.
Редактировать
Есть еще одна половина вашего вопроса, а именно, как написать функцию test
, которая "соответствует шаблону" на входе, чтобы проверить, является ли она типом Int
, Integer
, Zq
и т. Д. Вы предоставляете этот фрагмент кода:
test :: (Num a) => a -> a
test (Integer x) = x+2
test (Int x) = x+1
test (Zq x) = x
Здесь нужно кое-что прояснить.
Haskell имеет три уровня объектов: уровень значения, уровень типа и уровень вида. Некоторые примеры вещей на уровне значений включают "Hello, world!"
, 42
, функцию \a -> a
или fix (\xs -> 0:1:zipWith (+) xs (tail xs))
. Некоторые примеры вещей на уровне типа включают Bool
, Int
, Maybe
, Maybe Int
и Monad m => m ()
. Некоторые примеры вещей на добром уровне включают *
и (* -> *) -> *
.
Уровни в порядке; объекты уровня значения классифицируются по объектам уровня типа, а объекты уровня типа классифицируются по объектам уровня вида. Мы записываем классификационные отношения, используя ::
, например, 32 :: Int
или "Hello, world!" :: [Char]
. (Уровень вида не слишком интересен для этого обсуждения, но *
классифицирует типы, а типы стрелок классифицируют конструкторы типов. Например, Int :: *
и [Int] :: *
, но [] :: * -> *
.)
Теперь, одним из самых основных свойств Haskell является то, что каждый уровень полностью изолирован. Вы никогда не увидите строку типа "Hello, world!"
в типе; Точно так же объекты уровня значения не передаются и не работают с типами. Кроме того, существуют отдельные пространства имен для значений и типов. Возьмите пример Maybe
:
data Maybe a = Nothing | Just a
Это объявление создает новое имя Maybe :: * -> *
на уровне типа и два новых имени Nothing :: Maybe a
и Just :: a -> Maybe a
на уровне значения. Один общий шаблон - использовать одно и то же имя для конструктора типа и для конструктора его значений, если он только один; например, вы можете увидеть
newtype Wrapped a = Wrapped a
, который объявляет новое имя Wrapped :: * -> *
на уровне типа и одновременно объявляет отличное имя Wrapped :: a -> Wrapped a
на уровне значения. Некоторые особенно распространенные (и сбивающие с толку примеры) включают ()
, который является одновременно объектом уровня значения (типа ()
) и объектом уровня типа (типа *
), и []
, который одновременно объект уровня значения (типа [a]
) и объект уровня типа (типа * -> *
). Обратите внимание, что тот факт, что объекты уровня значения и уровня типа пишутся одинаково в вашем источнике, является просто совпадением! Если вы хотите сбить с толку своих читателей, вы вполне можете написать
newtype Huey a = Louie a
newtype Louie a = Dewey a
newtype Dewey a = Huey a
, где ни одно из этих трех объявлений вообще не связано друг с другом!
Теперь мы можем, наконец, решить, что не так с test
выше: Integer
и Int
не являются конструкторами значений, поэтому их нельзя использовать в шаблонах. Помните - уровень значения и уровень типа изолированы, поэтому вы не можете помещать имена типов в определения значений! К настоящему времени вы могли бы захотеть написать test'
вместо:
test' :: Num a => a -> a
test' (x :: Integer) = x + 2
test' (x :: Int) = x + 1
test' (Zq x :: Zq a) = x
... но, увы, это не совсем так. Вещи уровня ценности не могут зависеть от вещей уровня типа. То, что вы можете сделать - это написать отдельных функций для каждого из типов Int
, Integer
и Zq a
:
testInteger :: Integer -> Integer
testInteger x = x + 2
testInt :: Int -> Int
testInt x = x + 1
testZq :: Num a => Zq a -> Zq a
testZq (Zq x) = Zq x
Затем мы можем вызвать соответствующую одну из этих функций, когда хотим выполнить тест. Поскольку мы на языке статической типизации, одна из этих функций будет применима к любой конкретной переменной.
Теперь немного обременительно не забывать вызывать правильную функцию, поэтому Haskell предлагает небольшое удобство: вы можете позволить компилятору выбрать одну из этих функций для вас во время компиляции. Этот механизм - большая идея позади классов. Это выглядит так:
class Testable a where test :: a -> a
instance Testable Integer where test = testInteger
instance Testable Int where test = testInt
instance Num a => Testable (Zq a) where test = testZq
Теперь, это выглядит , как будто есть одна функция с именем test
, которая может обрабатывать любые из Int
, Integer
или числовые Zq
- но на самом деле есть три функции, и компилятор прозрачно выбирает один для вас. И это важное понимание. Тип test
:
test :: Testable a => a -> a
... с первого взгляда выглядит так, будто это функция, которая принимает значение любого типа Testable
. Но на самом деле это функция, которая может быть специализирована для любого типа Testable
- и тогда только принимает значения этого типа! Это различие объясняет еще одну причину, по которой оригинальная функция test
не работала. У вас не может быть нескольких шаблонов с переменными разных типов, потому что функция когда-либо работает только с одним типом одновременно.
Идеи, стоящие за классами NamedType
и Testable
выше, можно немного обобщить; если вы это сделаете, вы получите класс Typeable
, предложенный Хаммаром выше.
Я думаю, что теперь я бродил более чем достаточно и, вероятно, перепутал больше вещей, чем объяснил, но оставьте мне комментарий, в котором говорится, какие части были неясными, и я сделаю все возможное.