typeclass с ограниченной функцией params

Я хочу написать такой класс:

class C c where
    op :: c -> c -> Bool

class A b => B b where
    func :: C c => b -> c -- ^ type 'c' is random(forall). 
    func2 :: b -> b -> Bool
    func2 x y = func b `op` func c

Здесь c — это тип, ограниченный C, и это ограничение будет использоваться в func2. Но это не может быть компилятор. Тип c не является реальным типом. Я пытаюсь добавить forall или использовать TypeFamilies, но ни один из них не может этого сделать. TypeFamilies выглядит хорошо, но его нельзя использовать с ограничениями в определении функции, такими как C c => b -> c или `type X x :: C * => *.

Должен ли я использовать (A b, C c) => B b c для определения этого класса? У меня есть другой класс, использующий B, например B b => D d b. При добавлении параметра для класса B классу D также требуется еще один параметр. На самом деле Seq a будет использоваться с классом D, который не может соответствовать D d b.

РЕДАКТИРОВАТЬ: еще одно описание.

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
module Main where

type Ta = (Integer, Integer)
newtype Tb t = Tb { tb :: [t] } deriving Show

class Eq a => A a where
    a1f :: Ord b => a -> b
    a2f :: a -> a -> Bool
    a2f x y = a1f x >= a1f y

instance A Ta where
    a1f (_, y) = y

class A a => B b a where
    op :: b a -> b a

instance B Tb Ta where
    op x = x

main :: IO ()
main = putStrLn $ show $ op $ (Tb [(1, 1)] :: Tb Ta)

Компилятор будет жаловаться на строку a2f :: b -> Bool:

    • Could not deduce (Ord a0) arising from a use of ‘>=’
      from the context: A a
        bound by the class declaration for ‘A’ at test.hs:10:15
      The type variable ‘a0’ is ambiguous
      These potential instances exist:
        instance Ord Ordering -- Defined in ‘GHC.Classes’
        instance Ord Integer
          -- Defined in ‘integer-gmp-1.0.2.0:GHC.Integer.Type’
        instance Ord a => Ord (Maybe a) -- Defined in ‘GHC.Maybe’
        ...plus 22 others
        ...plus four instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the expression: a1f x >= a1f y
      In an equation for ‘a2f’: a2f x y = a1f x >= a1f y

EDIT2: использовать семейства типов

...
class Eq a => A a where
    type AT a :: *
    a1f :: Ord (AT a) => a -> AT a
    a2f :: a -> a -> Bool
    a2f x y = a1f x >= a2f y

instance A Ta where
    type AT Ta = Integer
    a1f (_, y) = y
...

Он покажет ошибку с:

    • Could not deduce (Ord (AT a)) arising from a use of ‘>=’
      from the context: A a
        bound by the class declaration for ‘A’ at test.hs:10:15
    • In the expression: a1f x >= a1f y
      In an equation for ‘a2f’: a2f x y = a1f x >= a1f y

person Vonfry    schedule 23.09.2019    source источник
comment
forall не означает случайное. Это означает, что выбирает вызывающий. В этом случае вызывающим является func2, и вы должны сказать ему, как выбрать. Можете ли вы рассказать немного больше о том, чего вы пытаетесь достичь, чтобы мы могли дать лучший совет, как это исправить? Есть пара (противоречивых) путей продвижения вперед, и неясно, какой будет лучше для вас.   -  person Daniel Wagner    schedule 23.09.2019
comment
@DanielWagner Вся моя программа состоит из множества строк, и это всего лишь упражнение для другого моего курса. Я добавил пример в вопрос, который представляет собой минимальную проблему, которая у меня есть. Хочешь дать мне совет?   -  person Vonfry    schedule 23.09.2019
comment
Вам не нужно показывать нам всю вашу программу, чтобы объяснить, чего вы пытаетесь достичь, но вам нужно рассказать о вычислениях, которые вы хотите выполнить, и о том, почему вы считаете, что это неизбежно приводит вас к необходимости иметь класс типов эту форму для достижения этого вычисления.   -  person Daniel Wagner    schedule 24.09.2019


Ответы (2)


Наименьшее исправление кода вашего семейства типов, которое позволяет его компилировать, — это переместить требование ограничения Ord из места его создания туда, где оно потребляется:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ConstrainedClassMethods #-}

type Ta = (Integer, Integer)

class Eq a => A a where
    type AT a :: *
    a1f :: a -> AT a
    a2f :: Ord (AT a) => a -> a -> Bool
    a2f x y = a1f x >= a1f y

instance A Ta where
    type AT Ta = Integer
    a1f (_, y) = y

Если вы хотите запрашивать только Ord (AT a) при использовании реализации по умолчанию, вы можете использовать DefaultSignatures (и исключить ConstrainedClassMethods):

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DefaultSignatures #-}

type Ta = (Integer, Integer)

class Eq a => A a where
    type AT a :: *
    a1f :: a -> AT a
    a2f :: a -> a -> Bool
    default a2f :: Ord (AT a) => a -> a -> Bool
    a2f x y = a1f x >= a1f y

instance A Ta where
    type AT Ta = Integer
    a1f (_, y) = y

Однако эта структура класса типов чрезвычайно странная и однообразная. (Несколько красных флажков, которые он поднимает, когда я его читаю: что там делает это ограничение Eq? Почему существует класс только с одним экземпляром? Почему a2f находится внутри класса, а не снаружи? Почему a1f просто не является классом- полиморфная функция? Почему мы должны верить, что существует только одна каноническая функция выбора для каждого типа?)

Я хотел бы повторить, что вы должны рассказать нам больше о том, какую цель вы пытаетесь достичь с помощью этого, а не рассказывать о предлагаемых вами классах типов для достижения этой цели. Многое в этой архитектуре кричит: «новичок пытается использовать классы типов так же, как языки OO используют классы», что станет для вас постоянным источником несоответствий импеданса и бумажных вырезок. Я сильно подозреваю, что вам вообще не следует определять новый класс типов.

person Daniel Wagner    schedule 24.09.2019
comment
Спасибо за ваш совет. Я много искал о том, что такое класс типов, и думаю, что он мне здесь не нужен, просто кодирую структуру данных с некоторыми функциями и не определяю класс типов. - person Vonfry; 25.09.2019

В вашем коде проблема заключается просто в том, что c в func b `op` func c неоднозначно. Это не такая уж большая проблема: просто зафиксируйте выбор с помощью локальной подписи. Например.

func2 x y = func x `op` (func y :: Int)

Но это может быть не то, чего вы действительно хотите. Должен ли c быть параметром типа класса func или всего экземпляра? В последнем случае правильным подходом будет использование MPTC.

{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, TypeApplications #-}

class ∀ b c . (A b, C c) => B b c where
  func :: b -> c
  func2 :: b -> b -> Bool
  func2 x y = func @b @c b `op` func c

Или, если для каждого экземпляра имеет смысл только один c, тогда вам нужно семейство типов или fundep.

{-# LANGUAGE TypeFamilies #-}

class A b => B b where
  type Ct b :: *
  func :: b -> Ct b
  func2 :: b -> b -> Bool
  func2 x y = func b `op` func c
person leftaroundabout    schedule 23.09.2019
comment
Извините за мое неполное описание, и я добавил пример того, что хочу. op в func2 определяется в классе типов. Если я использую семейства типов, ограничение отсутствует, и op не может быть сопоставлено. - person Vonfry; 23.09.2019