Я хотел бы создать класс Haskell, который выполняет операции с несколькими типами данных. Например, я хотел бы добавить значение ко всем элементам структуры карты и получить новую карту.
Для этого я создал класс и экземпляры трех типов, используя прагму MultiParamTypeClasses и FlexibleInstances:
class OPER a b c where
add:: a -> b -> c
instance OPER (Maybe Double) (Maybe Double) (Maybe Double) where
add (Just a) (Just b) = Just (a + b)
add _ _ = Nothing
instance OPER (Map.Map Int (Maybe Double)) (Maybe Double) (Map.Map Int (Maybe Double)) where
add ma mbdlb = Map.map (\mbi -> add mbi mbdlb ) ma
Он работает нормально и дает мне то, что я хочу:
main = do
print $ (add (Just (3.5::Double)) (Just (9.7::Double)) :: (Maybe Double))
print $ (add (Map.fromList [(1,Just 3.4),(2,Just 9.4),(3::Int,Just (9.7::Double)),(4,Just 4.8)]) (Just (9.7::Double)) :: (Map.Map Int (Maybe Double)))
Just 13.2
fromList [(1,Just 13.1),(2,Just 19.1),(3,Just 19.4),(4,Just 14.5)]
Теперь я хотел бы внедрить все возможные типы в тип AnyData. Я создал следующее:
data AnyData = forall s . (Show s, Eq s) => DAT s
и экземпляры:
instance Show AnyData where
show (DAT a) = "DAT "++show a
instance OPER AnyData AnyData AnyData where
add (DAT a) (DAT b) = DAT (add a b)
Но когда я пытаюсь скомпилировать, я получаю сообщение:
• Could not deduce (OPER s s1 s0) arising from a use of ‘add’
from the context: (Show s, Eq s)
bound by a pattern with constructor:
DAT :: forall s. (Show s, Eq s) => s -> AnyData,
in an equation for ‘add’
at testMultiFun_test5.hs:40:8-12
or from: (Show s1, Eq s1)
bound by a pattern with constructor:
DAT :: forall s. (Show s, Eq s) => s -> AnyData,
in an equation for ‘add’
at testMultiFun_test5.hs:40:16-20
The type variable ‘s0’ is ambiguous
Relevant bindings include
b :: s1 (bound at testMultiFun_test5.hs:40:20)
a :: s (bound at testMultiFun_test5.hs:40:12)
• In the first argument of ‘DAT’, namely ‘(add a b)’
In the expression: DAT (add a b)
In an equation for ‘add’: add (DAT a) (DAT b) = DAT (add a b)
|
40 | add (DAT a) (DAT b) = DAT (add a b)
| ^^^^^^^
Я попытался изменить объявление типа AnyData с помощью:
data AnyData = forall s . (OPER s s s, Show s, Eq s) => DAT s
или
data AnyData = forall s s1 s2 . (OPER s s1 s2) => DAT s
с прагмой AllowAmbigousTypes.
и проще:
data AnyData = forall s . DAT s
но у меня всегда одно и то же сообщение.
• No instance for (OPER s s1 s0) arising from a use of ‘add’
• In the first argument of ‘DAT’, namely ‘(add a b)’
In the expression: DAT (add a b)
In an equation for ‘add’: add (DAT a) (DAT b) = DAT (add a b)
|
43 | add (DAT a) (DAT b) = DAT (add a b)
|
Насколько я понимаю, компилятор не понимает, что моя функция добавления может иметь разные типы, когда я использую ее с AnyData, и не может разрешить вывод типов.
Есть ли решение встроить все типы, используемые в моем классе OPER, в один тип (чтобы составить список результатов)?
Есть ли другие способы сделать это? с GADT или семействами типов?
Извините, но я не понимаю, что вы имеете в виду.
Вам могут понравиться Призраки ушедших Доказательства.
Ваш AnyData
не говорит, какой тип данных хранится внутри него (в этом вся суть того, что вы пытаетесь сделать). Поэтому, когда вы говорите add (DAT a) (DAT b) = DAT (add a b)
, невозможно узнать, что такое типы a
и b
, и, следовательно, нет способа узнать, существует ли экземпляр OPER a b c
, который позволил бы вам add
их. И вы не можете добавлять ограничения к самому AnyData
, чтобы гарантировать наличие нужного вам экземпляра OPER
, поскольку необходимая информация о типе касается не одного AnyData
, а двух из них, используемых вместе (3, включая результат).
Я думаю, что самое близкое к тому, что вы просили напрямую, — это что-то вроде hackage.haskell.org/package/base-4.20.0.1/docs/… (поэтому не нужно создавать свои собственные AnyData
), что дает вам достаточно информации о типах во время выполнения, чтобы выполнить все проверки типов самостоятельно (но вам придется делать это самостоятельно). Я не думаю, что сам нашел бы это приемлемое решение, и думаю, что, вероятно, есть лучший ответ для вашей реальной цели, но для этого потребуется сделать шаг назад и по-другому подойти к тому, для чего вы этого хотите (то есть, по-другому). вопрос действительно).
@JeanJouX Пример Cubic - это проблема, с которой вы столкнулись. Если вы разрешите instance OPER AnyData AnyData AnyData
, то вы, по сути, разрешаете добавлять любой тип к любому типу, даже если эти типы различны и не могут быть добавлены вместе. В кубе используется Dat (Just 3)
, который скрывает значение Maybe Int
под Dat
, так что результирующий тип — AnyData
. Затем Dat (M.fromList [(1, Just 2)]
аналогичным образом скрывает Map Int (Maybe Int)
под Dat
, поэтому результирующий тип — AnyData
. Как мы можем осмысленно сложить эти два значения типа AnyData
? Поскольку мы не можем, instance
не может существовать.
Добавьте параметр типа в AnyData и можете использовать GADT для меньшего количества ограничений в объявлениях экземпляров.
Тип параметра для AnyData
Весь код здесь и ниже работает с GHC версии 8.8.4, также с использованием переключателя компилятора -Wall
.
{-# LANGUAGE MultiParamTypeClasses #-}
class OPER a b c where
add :: a -> b -> c
data AnyData s = DAT s
instance Show s => Show (AnyData s) where
show (DAT a) = "DAT " ++ show a
instance (OPER a a a) => OPER (AnyData a) (AnyData a) (AnyData a) where
add (DAT x1) (DAT x2) = DAT (add x1 x2)
instance OPER Int Int Int where
add nA nB = nA + nB
main :: IO ()
main =
do
print ( add ( DAT ( 7 :: Int ) ) ( DAT ( 13 :: Int ) ) :: (AnyData Int) )
GADT
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GADTs #-}
class OPER a b c where
add :: a -> b -> c
data AnyData s where
DAT :: (OPER s s s) => s -> AnyData s
instance Show s => Show (AnyData s) where
show (DAT a) = "DAT " ++ show a
instance OPER (AnyData s) (AnyData s) (AnyData s) where
add (DAT x1) (DAT x2) = DAT (add x1 x2)
instance OPER Int Int Int where
add nA nB = nA + nB
main :: IO ()
main =
do
print ( add ( DAT ( 7 :: Int ) ) ( DAT ( 13 :: Int ) ) :: (AnyData Int) )
Преимущество GADT заключается в том, что вам не нужно добавлять ограничение в объявление экземпляра для OPER (AnyData a) (AnyData a) (AnyData a)
.
Я нашел еще одну устаревшую альтернативу, использующую ограничение в объявлении типа данных.
Компилятор предупреждает: «-XDatatypeContexts устарел: он был широко признан ошибкой и был удален из языка Haskell».
Кроме того, вам необходимо применить UndecidableInstances, чтобы избежать следующей ошибки:
• Variable ‘s’ occurs more often
in the constraint ‘OPER s s s’
than in the instance head ‘Show (AnyData s)’
(Use UndecidableInstances to permit this)
• In the instance declaration for ‘Show (AnyData s)’
|
17 | instance (OPER s s s, Show s) => Show (AnyData s) where
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
УСТАРЕЛО: ограничение в объявлении типа данных (DatatypeContexts).
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DatatypeContexts #-}
{-# LANGUAGE UndecidableInstances #-}
module X2
(
main
)
where
class OPER a b c where
add :: a -> b -> c
data (OPER s s s, Show s) => AnyData s = DAT s
instance (OPER s s s, Show s) => Show (AnyData s) where
show (DAT a) = "DAT " ++ show a
instance (OPER s s s, Show s) => OPER (AnyData s) (AnyData s) (AnyData s) where
add (DAT x1) (DAT x2) = DAT (add x1 x2)
instance OPER Int Int Int where
add nA nB = nA + nB
main :: IO ()
main =
do
print ( add ( DAT ( 7 :: Int ) ) ( DAT ( 13 :: Int ) ) :: (AnyData Int) )
ПРИМЕЧАНИЕ. При применении добавления вам также необходимо добавить результирующий тип, поскольку компилятор не может сделать вывод (см. последнюю строку кода (AnyData Int)
).
Это то, чего вы хотели достичь?
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
import qualified Data.Map as Map
class OPER a b c where
add :: a -> b -> c
data AnyData s where
DAT :: (OPER s s s) => s -> AnyData s
instance Show s => Show (AnyData s) where
show (DAT a) = "DAT " ++ show a
instance OPER (AnyData s) (AnyData s) (AnyData s) where
add (DAT x1) (DAT x2) = DAT (add x1 x2)
instance OPER Int Int Int where
add nA nB = nA + nB
instance OPER (Map.Map Int (Maybe Double)) (Maybe Double) (Map.Map Int (Maybe Double)) where
add ma mbdlb = Map.map (\mbi -> add mbi mbdlb ) ma
instance OPER (Maybe Double) (Maybe Double) (Maybe Double) where
add (Just dA) (Just dB) = Just (dA + dB)
add Nothing _ = Nothing
add _ Nothing = Nothing
main :: IO ()
main =
do
print ( add ( DAT ( 7 :: Int ) ) ( DAT ( 13 :: Int ) ) :: (AnyData Int) )
print (add (Map.fromList [(1,Just 3.4),(2,Just 9.4),(3::Int,Just (9.7::Double)),(4,Just 4.8)]) (Just (9.7::Double)) :: (Map.Map Int (Maybe Double)))
Выход
DAT 20
fromList [(1,Just 13.1),(2,Just 19.1),(3,Just 19.4),(4,Just 14.5)]
Примечание. -XDatatypeContexts
устарел, поскольку не делает ничего полезного. Все, что он делает, это добавляет ограничение в конструктор. Это в значительной степени не приводит к тому, что это ограничение становится доступным при сопоставлении конструктора.
Подсказка: подумайте, каким должен быть
add (Dat (Just 3)) (Dat (M.fromList [(1, Just 2)])
...