4

Consider the following definitions:

class Foo a where
    foo :: a -> Int

class Bar a where
    bar :: a -> [Int]

Now, how do I say "every Foo is also a Bar, with bar defined by default as bar x = [foo x]" in Haskell?

(Whatever I try, the compiler gives me "Illegal instance declaration" or "Constraint is no smaller than the instance head")

Btw, I can define my Foo and Bar classes in some other way, if this would help.

mik01aj
  • 11,928
  • 15
  • 76
  • 119
  • 1
    This is related to (duplicate of?) http://stackoverflow.com/questions/3213490/how-do-i-write-if-typeclass-a-then-a-is-also-an-instance-of-b-by-this-definiti/3214201#3214201 – John L Dec 17 '10 at 17:13

3 Answers3

12
class Foo a where
    foo :: a -> Int

-- 'a' belongs to 'Bar' only if it belongs to 'Foo' also
class Foo a => Bar a where
    bar :: a -> [Int]
    bar x = [foo x] -- yes, you can specify default implementation

instance Foo Char where
    foo _ = 0

-- instance with default 'bar' implementation
instance Bar Char
max taldykin
  • 12,459
  • 5
  • 45
  • 64
  • 1
    This is much friendlier in terms of modularity than the `instance Foo a => Bar a` solution. – luqui Dec 17 '10 at 12:18
  • 1
    But shouldn't author's requirement of "every Foo is also a Bar" be expressed with 'class Bar a => Foo a where' not vice versa like here? – Ed'ka Dec 17 '10 at 12:25
  • 1
    But it looks to me that m01 actually meant 'class Foo a => Bar' a where' or otherwise it doesn't make sense to define bar via foo – Ed'ka Dec 17 '10 at 12:54
4

As the automatic definition of a Bar instance through a Foo instance can lead to undecidable cases for the compiler - i.e. one explicit instance and one through Foo conflicting with each other - , we need some special options to allow the desired behaviour. The rest through is quite straigtforward.

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

class Foo a where
    foo :: a -> Int

class Bar a where
    bar :: a -> [Int]

instance (Foo a) => Bar a where
    bar x = [foo x]
Dario
  • 48,658
  • 8
  • 97
  • 130
  • 4
    If you plan to have any more instances of `Bar` (and doing this is pointless without), then you need `OverlappingInstances` too. This causes type inference to have weird results sometimes (always use signatures when talking about these classes), and is generally a sign that you are doing something wrong. – luqui Dec 17 '10 at 12:16
  • 3
    And never publish a module with such an instance in it. It is rude (it causes wicked modularity problems). Always prefer max taldykin's solution if you plan on sharing. – luqui Dec 17 '10 at 12:21
  • 2
    Really, you just shouldn't do this. Even if you don't publish it, you'll most likely have other subtle problems. – John L Dec 17 '10 at 17:18
4

Generally speaking you don't model things with type classes this way[*] - i.e. an instance of a type class should always be some concrete type, though that type itself can be parameteric - e.g. the Show instance for pair has this signature:

instance (Show a, Show b) => Show (a,b) where

Some of the approaches to "Generics" allow you to model a general base case and then have type specific exceptional cases. SYB3 allowed this - perhaps unfortunately SYB3 isn't the common practice Generics library it is Data.Data / Data.Generics which I think is SYB1.

[*] In the wild the story is a little more complicated - as Dario says UndecidableInstances can enable it.

stephen tetley
  • 4,465
  • 16
  • 18