Преобразовать экземпляры семейства типов в Int - PullRequest
4 голосов
/ 14 марта 2012

У меня есть этот код:

type family Id obj :: *
type instance Id Box = Int

И я хочу сделать так, чтобы я всегда мог получить Int из семейства типов Id.Я понимаю, что потребуется преобразование.

Я подумал, что, возможно, создание класса сработает:

class IdToInt a where
  idToInt :: Id a -> Int

instance IdToInt Box where
  idToInt s = s

И это на самом деле компилируется.Но когда я пытаюсь использовать его:

testFunc :: Id a -> Int
testFunc x = idToInt x

я получаю ошибку:

src/Snowfall/Spatial.hs:29:22:
Couldn't match type `Id a0' with `Id a'
NB: `Id' is a type function, and may not be injective
In the first argument of `idToInt', namely `x'
In the expression: idToInt x
In an equation for `testFunc': testFunc x = idToInt x

Итак, как мне создать преобразование для идентификатора семейства типов, чтобы получить Int?

Основываясь на ответе ehird, я попробовал следующее, но оно тоже не работает:

class IdStuff a where
  type Id a :: *
  idToInt :: Id a -> Int

instance IdStuff Box where
  type Id Box = Int
  idToInt s = s

testFunc :: (IdStuff a) => Id a -> Int
testFunc x = idToInt x

Выдает ошибку:

src/Snowfall/Spatial.hs:45:22:
Could not deduce (Id a0 ~ Id a)
from the context (IdStuff a)
  bound by the type signature for
             testFunc :: IdStuff a => Id a -> Int
  at src/Snowfall/Spatial.hs:45:1-22
NB: `Id' is a type function, and may not be injective
In the first argument of `idToInt', namely `x'
In the expression: idToInt x
In an equation for `testFunc': testFunc x = idToInt x

Ответы [ 3 ]

3 голосов
/ 14 марта 2012

Вы не можете использовать функцию типа IdToInt a => Id a -> Int, потому что нет способа определить, что типа a. Следующий пример демонстрирует это.

type family Id a :: *
type instance Id () = Int
type instance Id Char = Int

class IdToInt a where idToInt :: Id a -> Int

instance IdToInt () where idToInt x = x + 1
instance IdToInt Char where idToInt x = x - 1

main = print $ idToInt 1

Поскольку Id () = Id Char = Int, тип idToInt в вышеприведенном контексте равен Int -> Int, что равно Id () -> Int и Id Char -> Int. Помните, что перегруженные методы выбираются в зависимости от типа. Оба экземпляра класса определяют idToInt функции, имеющие тип Int -> Int, поэтому средство проверки типов не может решить, какой из них использовать.

Вы должны использовать семейство данных вместо семейства типов и объявлять экземпляры новых типов.

data family Id a :: *
newtype instance Id () = IdUnit Int
newtype instance Id Char = IdChar Int

В экземпляре newtype Id () и Id Char оба являются целыми числами, но имеют разные типы. Тип Id информирует средство проверки типов о том, какую перегруженную функцию использовать.

3 голосов
/ 14 марта 2012

Вы не можете. Вам понадобится testFunc :: (IdToInt a) => Id a -> Int. Типовые семьи открыты, поэтому любой может объявить

type instance Id Blah = ()

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

class HasId a where
  type Id a
  idToInt :: Id a -> Int

instance IdToInt Box where
  type Id Box = Int
  idToInt s = s

Вам все равно понадобится контекст.

2 голосов
/ 14 марта 2012

Как уже отмечали другие, проблема в том, что компилятор не может определить, какой a использовать. Семейства данных - это одно решение, но альтернативой, с которой иногда проще работать, является использование свидетеля типа.

Измените свой класс на

class IdToInt a where
  idToInt :: a -> Id a -> Int

instance IdToInt Box where
  idToInt _ s = s

-- if you use this a lot, it's sometimes useful to create type witnesses to use
box = undefined :: Box

-- you can use it like
idToInt box someId

-- or
idToInt someBox (getId someBox)

Вопрос, на который вам нужно ответить, для любого данного Id, есть ли только один тип a, с которым он должен появиться? То есть существует ли однозначное соответствие между a с и Id a с? Если это так, семейства данных являются правильным подходом. Если нет, вы можете предпочесть свидетеля.

...