开发者

How do I write, "if typeclass a, then a is also an instance of b by this definition."

开发者 https://www.devze.com 2023-01-06 10:20 出处:网络
I have a typeclass MyClass, and there is a function in it which produces a String.I want to use this to imp开发者_C百科ly an instance of Show, so that I can pass types implementing MyClass to show.So

I have a typeclass MyClass, and there is a function in it which produces a String. I want to use this to imp开发者_C百科ly an instance of Show, so that I can pass types implementing MyClass to show. So far I have,

class MyClass a where
    someFunc :: a -> a
    myShow :: a -> String 

instance MyClass a => Show a where
    show a = myShow a

which gives the error Constraint is no smaller than the instance head. I also tried,

class MyClass a where
    someFunc :: a -> a
    myShow :: a -> String

instance Show (MyClass a) where
    show a = myShow a

which gives the error, ClassMyClass' used as a type`.

How can I correctly express this relationship in Haskell? Thanks.

I should add that I wish to follow this up with specific instances of MyClass that emit specific strings based on their type. For example,

data Foo = Foo
data Bar = Bar

instance MyClass Foo where
    myShow a = "foo"

instance MyClass Bar where
    myShow a = "bar"

main = do
    print Foo
    print Bar


I wish to vigorously disagree with the broken solutions posed thus far.

instance MyClass a => Show a where
    show a = myShow a

Due to the way that instance resolution works, this is a very dangerous instance to have running around!

Instance resolution proceeds by effectively pattern matching on the right hand side of each instance's =>, completely without regard to what is on the left of the =>.

When none of those instances overlap, this is a beautiful thing. However, what you are saying here is "Here is a rule you should use for EVERY Show instance. When asked for a show instance for any type, you'll need an instance of MyClass, so go get that, and here is the implementation." -- once the compiler has committed to the choice of using your instance, (just by virtue of the fact that 'a' unifies with everything) it has no chance to fall back and use any other instances!

If you turn on {-# LANGUAGE OverlappingInstances, IncoherentInstances #-}, etc. to make it compile, you will get not-so-subtle failures when you go to write modules that import the module that provides this definition and need to use any other Show instance. Ultimately you'll be able to get this code to compile with enough extensions, but it sadly will not do what you think it should do!

If you think about it given:

instance MyClass a => Show a where
    show = myShow

instance HisClass a => Show a where
    show = hisShow

which should the compiler pick?

Your module may only define one of these, but end user code will import a bunch of modules, not just yours. Also, if another module defines

instance Show HisDataTypeThatHasNeverHeardOfMyClass

the compiler would be well within its rights to ignore his instance and try to use yours.

The right answer, sadly, is to do two things.

For each individual instance of MyClass you can define a corresponding instance of Show with the very mechanical definition

instance MyClass Foo where ...

instance Show Foo where
    show = myShow

This is fairly unfortunate, but works well when there are only a few instances of MyClass under consideration.

When you have a large number of instances, the way to avoid code-duplication (for when the class is considerably more complicated than show) is to define.

newtype WrappedMyClass a = WrapMyClass { unwrapMyClass :: a }

instance MyClass a => Show (WrappedMyClass a) where
    show (WrapMyClass a) = myShow a

This provides the newtype as a vehicle for instance dispatch. and then

instance Foo a => Show (WrappedFoo a) where ...
instance Bar a => Show (WrappedBar a) where ...

is unambiguous, because the type 'patterns' for WrappedFoo a and WrappedBar a are disjoint.

There are a number of examples of this idiom running around in the the base package.

In Control.Applicative there are definitions for WrappedMonad and WrappedArrow for this very reason.

Ideally you'd be able to say:

instance Monad t => Applicative t where
    pure = return
    (<*>) = ap 

but effectively what this instance is saying is that every Applicative should be derived by first finding an instance for Monad, and then dispatching to it. So while it would have the intention of saying that every Monad is Applicative (by the way the implication-like => reads) what it actually says is that every Applicative is a Monad, because having an instance head 't' matches any type. In many ways, the syntax for 'instance' and 'class' definitions is backwards.


(Edit: leaving the body for posterity, but jump to the end for the real solution)

In the declaration instance MyClass a => Show a, let's examine the error "Constraint is no smaller than the instance head." The constraint is the type class constraint to the left of '=>', in this case MyClass a. The "instance head" is everything after the class you're writing an instance for, in this case a (to the right of Show). One of the type inference rules in GHC requires that the constraint have fewer constructors and variables than the head. This is part of what are called the 'Paterson Conditions'. These exist as a guarantee that type checking terminates.

In this case, the constraint is exactly the same as the head, i.e. a, so it fails this test. You can remove the Paterson condition checks by enabling UndecidableInstances, most likely with the {-# LANGUAGE UndecidableInstances #-} pragma.

In this case, you're essentially using your class MyClass as a typeclass synonym for the Show class. Creating class synonyms like this is one of the canonical uses for the UndecidableInstances extension, so you can safely use it here.

'Undecidable' means that GHC can't prove typechecking will terminate. Although it sounds dangerous, the worst that can happen from enabling UndecidableInstances is that the compiler will loop, eventually terminating after exhausting the stack. If it compiles, then obviously typechecking terminated, so there are no problems. The dangerous extension is IncoherentInstances, which is as bad as it sounds.

Edit: another problem made possible by this approach arises from this situation:

instance MyClass a => Show a where

data MyFoo = MyFoo ... deriving (Show)

instance MyClass MyFoo where

Now there are two instances of Show for MyFoo, the one from the deriving clause and the one for MyClass instances. The compiler can't decide which to use, so it will bail out with an error message. If you're trying to make MyClass instances of types you don't control that already have Show instances, you'll have to use newtypes to hide the already-existing Show instances. Even types without MyClass instances will still conflict because the definition instance MyClass => Show a because the definition actually provides an implementation for all possible a (the context check comes in later; its not involved with instance selection)

So that's the error message and how UndecidableInstances makes it go away. Unfortunately it's a lot of trouble to use in actual code, for reasons Edward Kmett explains. The original impetus was to avoid specifying a Show constraint when there's already a MyClass constraint. Given that, what I would do is just use myShow from MyClass instead of show. You won't need the Show constraint at all.


I think it would be better to do it the other way around:

class Show a => MyClass a where
    someFunc :: a -> a

myShow :: MyClass a => a -> String
myShow = show


You can compile it, but not with Haskell 98, You have to enable some language extensions :

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
-- at the top of your file

Flexible instances is there to allow context in instance declaration. I don't really know the meaning of UndecidableInstances, but I would avoid as much as I can.


As Ed Kmett pointed out, this is not possible at all for your case. If however you have access to the class you want to provide a default instance for, you can reduce the boilerplate to a minimum with a default implementation and constrain the input type with the default signature you need:

{-# LANGUAGE DefaultSignatures #-}

class MyClass a where
    someFunc :: a -> Int

class MyShow a where
    myShow :: a -> String
    default myShow :: MyClass a => a -> String
    myShow = show . someFunc

instance MyClass Int where
    someFunc i = i

instance MyShow Int

main = putStrLn (myShow 5)

Note that the only real boilerplate (well, apart from the whole example) reduced to instance MyShow Int.

See aesons ToJSON for a more realistic example.


You may find some interesting answers in a related SO question: Linking/Combining Type Classes in Haskell

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号