3

I am new comer of Haskell and following is my question:

given this class:

class MyClass a where
    foo :: a -> [a]

then I have a subclass that is more specific:

class (MyClass a) => SubClass a where
    foo param = [bar param]
    bar :: a -> a

but it doesn't work as expected. I was expecting a default implementation is setup in the definition of SubClass but it doesn't. I will need to define the instance for MyClass seperately, but that sounds stupid. How can I achieve default implementation when I know some subclass satisfies some property definitely?

More specifically, I want to express in Haskell, that when a class satisfies some properties, some functions for its parent can have default implementation. In my example, SubClass has property bar such that I know foo is definitely defined in such a way.

A more general form of this question is, is it a good idea to reuse by using classes and instances?

I found this post: Inclusion of typeclasses with default implementation in Haskell

It's quite close, but still not answering my question totally and their forms are little bit different.

Community
  • 1
  • 1
Jason Hu
  • 6,239
  • 1
  • 20
  • 41
  • The implementation of `foo` needs to be attached to the class that `foo` is a member of. What would you expect to happen if there was a second subclass of `MyClass` that also tried to define a default implementation of `foo` like this? – Reid Barton Jul 24 '15 at 01:01
  • @ReidBarton i want to express `implementation of foo is unknown at the point of MyClass but given some property of SubClass, the default implementation of foo can be decided`. – Jason Hu Jul 24 '15 at 01:09
  • Then see Daniel Wagner's answer, but note that you have to choose what the subclass `SubClass` is when you define `MyClass`. – Reid Barton Jul 24 '15 at 01:13
  • @ReidBarton yeah. i've read that. that's not the most desirable. wouldn't it be able to define an instance with constraint to deal with that? – Jason Hu Jul 24 '15 at 01:14

2 Answers2

6

Elaborating on Daniel's answer:

Suppose you have a new datatype MyData, defined as

data MyData = D1 | D2

You want to make MyData an instance of SubClass. You try the obvious solution first.

instance SubClass MyData where
    bar x = case x of { D1 -> D2 ; D2 -> D1 }

A quick examination of the type signatures, though, reveals that this won't work, because a type has to be an instance of MyClass before it can be an instance of SubClass. So you make MyData an instance of MyClass.

instance MyClass MyData

Again, this raises an error, because foo is included in the minimal complete definition of MyClass. For your instance to work, you would have to manually define foo, thus defeating the purpose of the default declaration.

In short, there is no way to do this in basic Haskell98 (or Haskell2010, for that matter). Thankfully, however, GHC provides a useful extension called DefaultSignatures. So, using Daniel's example:

{-# LANGUAGE DefaultSignatures #-}

class MyClass a where
    foo :: a -> [a]
    default foo :: SubClass a => a -> [a]
    foo param = [param]

class MyClass a => SubClass a where
    bar :: a -> a

And now, you can define the instances and they will work as you would expect. The downside to this solution is that the default definition has to be defined in MyClass, but this is necessary. The definition of foo belongs to the definition of MyClass (or one of its instance declarations), so if you were able to define the default declaration of foo within the definition of SubClass, Haskell's type isolation would be broken.

Kwarrtz
  • 2,693
  • 15
  • 24
  • thanks for your elaboration. it's very clear now. however, what about defining an instance of `MyClass` for all `SubClass`? something like `instance (SubClass a) => MyClass a`? what prevents me from doing that? – Jason Hu Jul 24 '15 at 01:34
  • @HuStmpHrrr : A declaration like that would fail the ambiguity check. Type declarations with ambiguous type declarations (ones with types like `a`) only work when the type of the placeholder can be inferred from context. For example, if you have a function with the signature `[a] -> Int`, and then call it with `['c','$','\n']`, it can infer the type of `a` to be `Char`, so it succeeds. In your example, there is no context to speak of, and so cannot determine the type of `a`. This is the same reason that a list of type `[a]` must still be homogeneous. – Kwarrtz Jul 24 '15 at 01:45
5

This can be achieved with DefaultSignatures:

{-# LANGUAGE DefaultSignatures #-}

class MyClass a where
    foo :: a -> [a]
    default foo :: SubClass a => a -> [a]
    foo param = [param]

class MyClass a => SubClass a where
    bar :: a -> a

Testing it in ghci:

> instance MyClass Integer; instance SubClass Integer where bar = id
> foo 3
[3]
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • this is nice, but i am expecting the default implementation appear with `MyClass`. would that be achievable by defining an instance with constraint? like `instance (SubClass a) => MyClass a` but it looks quite weird. – Jason Hu Jul 24 '15 at 01:11
  • @HuStmpHrrr I'm not sure I understand your objection. Can you clarify further? In particular, you say you expect the default implementation to appear in `MyClass`; but it does in fact appear there. So what's the problem? – Daniel Wagner Jul 24 '15 at 01:19
  • oh crap my brain. i meant **appear with `SubClass`**, since the default implementation belongs to `SubClass`. – Jason Hu Jul 24 '15 at 01:22
  • @HuStmpHrrr Suppose in your proposed world I wrote `class MyClass a => SubClass1 a where {foo x = [x]}; class MyClass a => SubClass2 a where {foo x = [x,x]}; instance SubClass1 Integer; instance SubClass2 Integer`. What would be the meaning of `foo 3` in that world, `[3]` or `[3,3]`? – Daniel Wagner Jul 24 '15 at 01:26
  • i see your point. but wouldn't default signatures have the same problem? – Jason Hu Jul 24 '15 at 01:30
  • @HuStmpHrrr Default signatures don't have this problem: the default implementation is associated with the method it is implementing, so it is easy for the compiler to only let you write one without violating the open world assumption or losing separate compilation. – Daniel Wagner Jul 24 '15 at 02:58
  • Is it possible to use the default implementation when defining a new one ? It's a common pattern in OO to just *decorate* a parent method. – mb14 Jul 24 '15 at 12:58
  • @mb14 You may of course give it its own name; e.g. `subClassDef param = [param]` at the top level and then write `foo = subClassDef` in the declaration of `MyClass`. – Daniel Wagner Jul 24 '15 at 18:01
  • @mb14 ...but it sounds to me like you are trying to use Haskell classes as if they were OO-style classes. This is bound to cause pain: Haskell classes are a collection of types, whereas OO-style classes are generally a single, distinguished type. – Daniel Wagner Jul 24 '15 at 18:04
  • I'm not doing anything, I'm just asking a question ;-) – mb14 Jul 24 '15 at 19:13