Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
826 views
in Technique[技术] by (71.8m points)

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

I have a typeclass MyClass, and there is a function in it which produces a String. I want to use this to imply 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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...