fulem
fulem

Reputation: 85

Haskell TypeClasses - Override superclass function

Consider a type class like the following:

class A where
  fun1 :: String   -- undefined function     
  fun2 :: String   -- function with default def using fun1
  fun2 = fun1

I want to define different sub classes extending capabilities and also be able to provide different definitions for inherited fun1. For example:

class A => B where
  fun3 :: String   -- other functions for B
  fun1 = "B"       -- provide def for super class fun1 

class A => C where
  fun4 :: String   -- other functions for C
  fun1 = "C"       -- provide def for super class fun1

When i instance B, i expect to inherit A capabilities (that is fun2) "customized" for B class. And similar for C.

But, compiler output:

error: ‘fun1’ is not a (visible) method of class ‘B’

I'm aware that type classes are not 100% like ordinary OOP. But, isn't possible to "override" inherited functions in child classes?.

Upvotes: 1

Views: 349

Answers (2)

AntC
AntC

Reputation: 2806

I want to define different sub classes extending capabilities and also be able to provide different definitions for inherited fun1.

(I'll leave aside for now why "inherited" isn't appropriate there.)

If you want 'to provide different definitions' for fun1, you need to provide something in the declaration of fun1 that'll hook into the differences. In other words, you need a type parameter:

(Heh heh Note to the brains trust: this got into deep waters very fast, and needs advanced typaholic extensions. Doesn't work in GHC 7.10, for example.)

class A a  where
  fun1 :: a -> String
  fun2 :: String
  fun2 = fun1 (undefined :: a)

Note the derived type for fun2 :: A a => String. It still mentions parameter a even though a doesn't appear as an argument nor result. For fun1 :: A a => a -> String, a does appear as an argument, but of course there's no need for fun1's instance decls to actually inspect its argument. And fun2's default definition passes argument undefined to fun1, so it expects fun1 won't inspect it. It's likely a is a 'phantom type' (many SO explanations of that term) parameter to the class. But we can't be sure until we see the instances.

(For advanced players: fun2 also passes type param a via ScopedTypeVariables and the type annotation :: a, or could be via TypeApplications. Needs AllowAmbiguousTypes to accept that sig for fun2.)

Now we can overload fun1, fun2 for different types

instance A Int where
  fun1 _ = "Int"                  -- ignores its arg, so that's a phantom
  -- fun2                         -- not overloaded, so takes default  

Going back to your

   class A => B where
     fun3 :: String   -- other functions for B
     fun1 = "B"       -- provide def for super class fun1 

How do you expect to access this override for fun1? There's no mechanism, that's why the compiler complains. Instead you need to provide a hook back to some overloading of fun1. could be

newtype DB a = DB a
class A (DB a) => B a  where
  fun3 :: String                  -- again a is phantom
  fun3 = fun1 (undefined :: DB a)

instance B Int  where
  -- fun3 = fun1 (undefined :: DB Int)

instance A (DB Int)  where
  fun1 _ = "B"

Coming back to your "inherited fun1": the linkage from class B must be via some type to some instance A with an overload for fun1. I've used a newtype to keep the linkage lightweight. It's not an inheritance mechanism in the sense of OO.

Upvotes: 1

leftaroundabout
leftaroundabout

Reputation: 120711

I'm aware that type classes are not 100% like ordinary OOP

As Willem commented, that's an understatement. Haskell typeclasses are completely and fundamentally not like OO classes. Put simply, a typeclass is a set of types, whereas an OO class is a set of objects. In Haskell, we call “objects” values, and a set of values is roughly what a type represents, not a class.

In other words, an OO class is more analogous to a single Haskell type than to a Haskell type class.

Again, a Haskell type is only roughly like a set of values. There are some important differences, notably Haskell doesn't support subtypes whereas an OO subclass does indeed represent a subset of the set of objects that the superclass represents.

If your example, reverse-engineered into C++, is

class A {
 public:
  virtual String fun1() =0;
  String fun2() { return fun1(); }
};

class B: public A {
 public:
  virtual String fun3() =0;
  String fun1() { return "B"; }
};

class B: public A {
 public:
  virtual String fun4() =0;
  String fun1() { return "B"; }
};

then the most direct translation to Haskell is:

data A = A { str1 :: String }

class SubsumesA a where
  fun2 :: a -> String

instance SubsumesA A where
  fun2 = str1

data B = B { superB :: A
           , str3 :: String }

instance SubsumesA B where
  fun2 = const "B"

data C = C { superC :: A
           , str2 :: String }

instance SubsumesA C where
  fun2 = const "C"

But this is generally not how you should go about writing Haskell type hierarchies. Haskell is not an OO language, don't treat it as if it were. Specifically, this style tends to run you into the existential antipattern.

Upvotes: 1

Related Questions