Как встроить несколько типов параметров в один контейнер типа с ограничением класса?

Я хотел бы создать класс 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 или семействами типов?

Подсказка: подумайте, каким должен быть add (Dat (Just 3)) (Dat (M.fromList [(1, Just 2)])...

Cubic 02.07.2024 16:36

Извините, но я не понимаю, что вы имеете в виду.

JeanJouX 02.07.2024 17:02

Вам могут понравиться Призраки ушедших Доказательства.

Daniel Wagner 02.07.2024 19:05

Ваш AnyData не говорит, какой тип данных хранится внутри него (в этом вся суть того, что вы пытаетесь сделать). Поэтому, когда вы говорите add (DAT a) (DAT b) = DAT (add a b), невозможно узнать, что такое типы a и b, и, следовательно, нет способа узнать, существует ли экземпляр OPER a b c, который позволил бы вам add их. И вы не можете добавлять ограничения к самому AnyData, чтобы гарантировать наличие нужного вам экземпляра OPER, поскольку необходимая информация о типе касается не одного AnyData, а двух из них, используемых вместе (3, включая результат).

Ben 03.07.2024 01:19

Я думаю, что самое близкое к тому, что вы просили напрямую, — это что-то вроде hackage.haskell.org/package/base-4.20.0.1/docs/… (поэтому не нужно создавать свои собственные AnyData), что дает вам достаточно информации о типах во время выполнения, чтобы выполнить все проверки типов самостоятельно (но вам придется делать это самостоятельно). Я не думаю, что сам нашел бы это приемлемое решение, и думаю, что, вероятно, есть лучший ответ для вашей реальной цели, но для этого потребуется сделать шаг назад и по-другому подойти к тому, для чего вы этого хотите (то есть, по-другому). вопрос действительно).

Ben 03.07.2024 01:25

@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 не может существовать.

chi 03.07.2024 13:17
Сила классов Java: сравнение с языком C
Сила классов Java: сравнение с языком C
Абстракция" - это процесс упрощения сложных сущностей или концепций реального мира с целью их применения в форме программирования. В Java класс...
1
6
79
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короткий ответ

Добавьте параметр типа в 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 устарел, поскольку не делает ничего полезного. Все, что он делает, это добавляет ограничение в конструктор. Это в значительной степени не приводит к тому, что это ограничение становится доступным при сопоставлении конструктора.

Carl 05.07.2024 03:55

Другие вопросы по теме