Все ваши методы напечатаны неоднозначно.
Чтобы лучше проиллюстрировать проблему, давайте сведем пример к одному методу:
class C a b c where
get :: a -> [b]
Теперь представьте, что у вас есть следующие экземпляры:
instance C Int String Bool where
get x = [show x]
instance C Int String Char where
get x = ["foo"]
А затем представьте, что вы пытаетесь вызвать метод:
s :: [String]
s = get (42 :: Int)
Из сигнатуры s
компилятор знает, что b ~ String
. Из параметра get
компилятор знает, что a ~ Int
. Но что такое c
? Компилятор не знает. Нигде не найти этого.
Но подождите! Оба экземпляра C
соответствуют a ~ Int
и b ~ String
, так что выбрать? Не ясно. Не хватает информации. Неоднозначно.
Это именно то, что происходит, когда вы пытаетесь вызвать get
и change
в map change . get
: недостаточно информации о типе для компилятора, чтобы понять, что a
, b
и c
предназначены для вызова get
или change
. Да, и имейте в виду: оба эти звонка могут исходить из разных случаев. Нельзя сказать, что они должны быть из того же самого экземпляра, что и changeAll
.
Существует два возможных способа исправить это.
Во-первых, вы можете используйте функциональную зависимость , которая означает, что для определения c
достаточно знать a
и b
:
class C a b c | a b -> c where ...
Если вы объявите таким образом, компилятор отклонит несколько экземпляров для одних и тех же a
и b
, но разных c
, и, с другой стороны, он сможет выбрать экземпляр, просто зная a
и b
.
Конечно, вы можете иметь несколько функциональных зависимостей в одном классе. Например, вы можете объявить, что знания двух любых переменных достаточно для определения третьей:
class C a b c | a b -> c, a c -> b, b c -> a where ...
Имейте в виду, что для вашей функции changeAll
даже этих трех функциональных зависимостей будет недостаточно, потому что реализация changeAll
"глотает" b
. То есть, когда он вызывает get
, единственный известный тип - a
. И точно так же, когда он вызывает change
, единственный известный тип - c
. Это означает, что для того, чтобы такое «проглатывание» b
сработало, оно должно определяться только a
, а также только c
:
class Interface a b c | a -> b, c -> b where ...
Конечно, это будет возможно только в том случае, если logi c вашей программы действительно обладает таким свойством, что некоторые переменные определяются другими. Если вам действительно нужно, чтобы все переменные были независимыми, читайте дальше.
Во-вторых, вы можете явно указать компилятору, какие типы должны быть , используя TypeApplications
:
s :: String
s = get @Int @String @Bool 42 -- works
Нет больше двусмысленности. Компилятор точно знает, какой экземпляр выбрать, потому что вы сказали это явно.
Применение этого к вашей реализации changeAll
:
changeAll :: a -> [c]
changeAll = map (change @a @b @c) . get @a @b @c
(ПРИМЕЧАНИЕ: для того, чтобы иметь возможность ссылаться Переменные типа a
, b
и c
в теле функции подобным образом, вам также необходимо включить ScopedTypeVariables
)
И, конечно, вам также необходимо сделать это при вызове changeAll
, поскольку в сигнатуре его типа недостаточно информации:
foo = changeAll @Int @String @Bool 42