Хаскель получает тип алгебраического параметра - PullRequest
3 голосов
/ 03 сентября 2011

У меня есть тип

class IntegerAsType a where
  value :: a -> Integer

data T5
instance IntegerAsType T5 where value _ = 5

newtype (IntegerAsType q) => Zq q = Zq Integer deriving (Eq)

newtype (Num a, IntegerAsType n) => PolyRing a n = PolyRing [a]

Я пытаюсь сделать хорошее "шоу" для типа PolyRing. В частности, я хочу, чтобы "show" распечатало type 'a'. Есть ли функция, которая возвращает тип алгебраического параметра ('show' для типов)?

Другой способ, которым я пытаюсь это сделать, - это использование сопоставления с образцом, но у меня возникают проблемы со встроенными типами и алгебраическим типом.

Я хочу разные результаты для каждого из Integer, Int и Zq q. (пример с игрушкой:)

test :: (Num a, IntegerAsType q) => a -> a
(Int x) = x+1
(Integer x) = x+2
(Zq x) = x+3

Здесь как минимум две разные проблемы.

1) Int и Integer не являются конструкторами данных для типов «Int» и «Integer». Существуют ли конструкторы данных для этих типов / как мне сопоставить шаблон с ними?

2) Хотя это не показано в моем коде, Zq является экземпляром Num. Проблема, которую я получаю:

Ambiguous constraint `IntegerAsType q'
At least one of the forall'd type variables mentioned by the constraint 
must be reachable from the type after the '=>'
In the type signature for `test':
test :: (Num a, IntegerAsType q) => a -> a

Я понимаю, почему это жалуется, но я не знаю, как обойти это.

Спасибо

EDIT: Лучший пример того, что я пытаюсь сделать с помощью тестовой функции:

test :: (Num a) => a -> a
test (Integer x) = x+2
test (Int x) = x+1
test (Zq x) = x

Даже если мы игнорируем тот факт, что я не могу построить целые и целые числа таким образом (все еще хочу знать, как!), Этот «тест» не компилируется, потому что:

Could not deduce (a ~ Zq t0) from the context (Num a)

Моя следующая попытка этой функции была с подписью типа:

test :: (Num a, IntegerAsType q) => a -> a

, что приводит к новой ошибке

Ambiguous constraint `IntegerAsType q'
At least one of the forall'd type variables mentioned by the constraint 
must be reachable from the type after the '=>'

Надеюсь, это прояснит мой вопрос ....

Ответы [ 2 ]

4 голосов
/ 03 сентября 2011

Я не уверен, к чему вы клоните с помощью этой функции 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, предложенный Хаммаром выше.

Я думаю, что теперь я бродил более чем достаточно и, вероятно, перепутал больше вещей, чем объяснил, но оставьте мне комментарий, в котором говорится, какие части были неясными, и я сделаю все возможное.

2 голосов
/ 03 сентября 2011

Есть ли функция, которая возвращает тип алгебраического параметра ('show' для типов)?

Я думаю Data.Typeable может быть то, что вы ищете.

Prelude> :m + Data.Typeable
Prelude Data.Typeable> typeOf (1 :: Int)
Int
Prelude Data.Typeable> typeOf (1 :: Integer)
Integer

Обратите внимание, что это не будет работать для любого типа , только для тех, у которых есть экземпляр Typeable. Используя расширение DeriveDataTypeable, вы можете сделать так, чтобы компилятор автоматически выводил их для ваших собственных типов:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Typeable

data Foo = Bar
  deriving Typeable
*Main> typeOf Bar
Main.Foo

Я не совсем понял, что вы пытаетесь сделать во второй половине вашего вопроса, но, надеюсь, это должно помочь.

...