Пожалуйста, извините меня, если я использую неправильную терминологию, я много начинаю в манипуляции типа haskell... Я пытаюсь использовать перекрывающиеся экземпляры с функциональными зависимостями для программирования на уровне типа с помощью HLists.
Здесь моя цель - попытаться написать typeclass HNoNils l l'
, где HNoNils l l'
означает, что при l
, являющемся типом списка (ex: Int : String : EmptyNil : Int : HNil
), l'
- это соответствующий тип списка без конкретный пустой тип EmptyNil
(результат примера: Int:String:Int:HNil
):
{-# LANGUAGE ExistentialQuantification,
FunctionalDependencies,
FlexibleInstances,
UndecidableInstances,
OverlappingInstances,
TypeFamilies #-}
import Data.HList
import Data.TypeLevel.Bool
--Type Equality operators
--usedto check if a type is equal to another
class TtEq a b eq | a b -> eq
instance TtEq a a True
instance eq~False => TtEq a b eq
data EmptyNil = EmptyNil deriving (Show) --class representing empty channel
--class intended to generate a list type with no type of EmptyNil
-- Example: HCons Int $ HCons EmptyNil HNil should give HCons Int HNil
class (HList list, HList out) => HNoNils list out | list -> out
where hNoNils :: list -> out
-- l gives l' means (HCons EmptyNil l) gives l'
instance (HNoNils l l',TtEq e EmptyNil True ) => HNoNils (HCons e l) l'
where hNoNils (HCons e l) = hNoNils l
-- l gives l' means (HCons e l) gives (HCons e l') for all e
instance (HNoNils l l') => HNoNils (HCons e l) (HCons e l')
where hNoNils (HCons e l) = hCons e $ hNoNils l
--base case
instance HNoNils HNil HNil
where hNoNils _ = hNil
testList = HCons EmptyNil $ HCons EmptyNil HNil
testList1 = HCons "Hello" $ HCons EmptyNil HNil
testList2 = HCons EmptyNil $ HCons "World" HNil
testList3 = HCons "Hello" $ HCons "World" HNil
main:: IO ()
main = do
print $ hNoNils testList -- should get HNil
print $ hNoNils testList1 -- should get HCons "Hello" HNil
print $ hNoNils testList2 -- should get HCons "World" HNil
print $ hNoNils testList3 -- should get HCons "Hello" (HCons "World" HNil)
Однако, когда я запускаю этот код как есть, все мои вызовы hNoNils
, кажется, разрешаются через наименьшее конкретное, второе объявление экземпляра, что означает (по крайней мере, похоже), что для всех l
у меня есть HNoNils l l
.
Основываясь на том, что я прочитал, с расширением OverlappingInstances
, система должна всегда разрешать наиболее конкретный экземпляр.
Здесь
-
первый экземпляр имеет ограничения
(HNoNils l l',TtEq e EmptyNil True )
-
второй экземпляр имеет ограничения
HNoNils l l'
Простите меня, если я ошибаюсь, но похоже, что первый экземпляр более специфичен, чем второй, поэтому он должен идти за этим, не так ли?
Я попытался добавить ограничения, чтобы попытаться избавиться от перекрытия, а именно, добавив отдельное ограничение oposite равенства ко второму экземпляру:
-- l gives l' means (HCons EmptyNil l) gives l'
instance (HNoNils l l',TtEq e EmptyNil True ) => HNoNils (HCons e l) l'
where hNoNils (HCons e l) = hNoNils l
-- l gives l' means (HCons e l) gives (HCons e l') for all e
-- added constraint of TtEq e EmptyNil False
instance (HNoNils l l',TtEq e EmptyNil False) => HNoNils (HCons e l) (HCons e l')
where hNoNils (HCons e l) = hCons e $ hNoNils l
Я попытался удалить перекрывающееся расширение экземпляра здесь, и я получаю ошибки перекрытия.
Overlapping instances for HNoNils
(HCons EmptyNil (HCons [Char] HNil)) (HCons EmptyNil l')
Matching instances:
instance (HNoNils l l', TtEq e EmptyNil True) =>
HNoNils (HCons e l) l'
-- Defined at /home/raphael/Dropbox/IST/AFRP/arrow.hs:32:10
instance (HNoNils l l', TtEq e EmptyNil False) =>
HNoNils (HCons e l) (HCons e l')
-- Defined at /home/raphael/Dropbox/IST/AFRP/arrow.hs:36:10
Я не понимаю второго матча. В конце концов, в этой резолюции мы имеем e равным EmptyNil, поэтому TtEq e EmptyNil True
... right? И в этом отношении, как система типов даже попадает в ситуацию, когда она задает этот вопрос, ведь с ограничениями у вас никогда не должно быть ситуации типа HNoNils (Hcons EmptyNil l) (HCons EmptyNil l'))
, по крайней мере, я так не думаю.
При добавлении обратно OverlappingInstances, я получаю еще более странные ошибки, которые я не понимаю:
Couldn't match type `True' with `False'
When using functional dependencies to combine
TtEq a a True,
arising from the dependency `a b -> eq'
in the instance declaration at /home/raphael/Dropbox/IST/AFRP/arrow.hs:23:14
TtEq EmptyNil EmptyNil False,
arising from a use of `hNoNils'
at /home/raphael/Dropbox/IST/AFRP/arrow.hs:53:13-19
In the second argument of `($)', namely `hNoNils testList2'
In a stmt of a 'do' block: print $ hNoNils testList2
второй оператор TtEq EmptyNil EmptyNil False
, кажется, говорит, что экземпляр был сгенерирован вызовом функции...? Я немного смущен относительно того, откуда он пришел.
Поэтому, пытаясь понять это, мне было интересно:
-
Можно ли получить более подробную информацию о том, как Haskell работает с экземплярами? Некоторые из этих комбинаций кажутся невозможными. Даже оценить связь с документом, объясняющим механизм, было бы оценено
-
Существует ли более конкретное определение того, как
OverlappingInstances
работает? Я чувствую, что мне что-то не хватает (например, аргумент "специфичность" работает только с переменными типа jthe, а не с количеством ограничений...)
edit. Поэтому я попробовал одно из предложений C.A. McCann (удаление некоторых ограничений) на следующее:
instance (HNoNils l l') => HNoNils (HCons EmptyNil l) l'
instance (HNoNils l l') => HNoNils (HCons e l) (HCons e l')
instance HNoNils HNil HNil
Выполнение этого дает мне некоторые неприятные ошибки совпадающих экземпляров:
Overlapping instances for HNoNils
(HCons EmptyNil (HCons [Char] HNil)) (HCons EmptyNil l')
arising from a use of `hNoNils'
Matching instances:
instance [overlap ok] HNoNils l l' => HNoNils (HCons EmptyNil l) l'
-- Defined at /Users/raphael/Dropbox/IST/AFRP/arrow.hs:33:10
instance [overlap ok] HNoNils l l' =>
HNoNils (HCons e l) (HCons e l')
-- Defined at /Users/raphael/Dropbox/IST/AFRP/arrow.hs:37:10
Мне кажется, что метод разрешения сверху вниз, а не снизу вверх (где система никогда не попытается найти такой экземпляр).
edit 2: добавив небольшое ограничение ко второму условию, я получил ожидаемое поведение (см. комментарий Макканна о его ответе):
-- l gives l' means (HCons EmptyNil l) gives l'
instance (HNoNils l l') => HNoNils (HCons EmptyNil l) l'
where hNoNils (HCons EmptyNil l) = hNoNils l
-- l gives l' means (HCons e l) gives (HCons e l') for all e
instance (HNoNils l l',r~ HCons e l' ) => HNoNils (HCons e l) r
where hNoNils (HCons e l) = hCons e $ hNoNils l
здесь добавленная вещь - это ограничение r~HCons e l'
для второго экземпляра.