Search code examples
haskelltypeclassforall

typeclass with constrained params' function


I want to write a class like this:

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

Here, c is a type restricted by C and this restriction will be used in func2. But this cannot be compiler. Type c is not a real type. I try to add forall or using TypeFamilies, but none of them can do this. TypeFamilies looks good, but it cannot use with restriction in funcion definition like C c => b -> c or `type X x :: C * => *.

Must I use (A b, C c) => B b c to define this class? I have another class using with B like B b => D d b. If adding a param for class B, the D class needs one more param as well. In fact, Seq a will be used with class D, which cannot match D d b.

EDIT: one more description.

{-# 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)

Compiler will complain with the line 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: Use type families

...
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
...

It will show error with:

    • 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

Solution

  • The smallest fix to your type families code that lets it compile is to move the demand for an Ord constraint from where it is produced to where it is consumed:

    {-# 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
    

    If you'd like to only demand Ord (AT a) when the default implementation is used, you can use DefaultSignatures (and eliminate 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
    

    However, this typeclass structure is exceeding strange and unidiomatic. (Some red flags it raises as I read it: What is that Eq constraint doing there? Why is there a class with just one instance? Why is a2f inside the class instead of outside? Why isn't a1f simply a non-class-polymorphic function? Why should we believe there is just one canonical selection function for each type?)

    I'd like to reiterate that you should tell us more about what goal you are trying to achieve with this, rather than talking about your proposed typeclasses for achieving that goal. Much about this architecture screams "beginner trying to use typeclasses the way OO languages use classes", which is going to be an ongoing source of impedance mismatches and papercuts for you. I strongly suspect you simply shouldn't be defining a new typeclass at all.