mjgpy3
mjgpy3

Reputation: 8947

String and [a] instances

As an exercise, I decided to implement a Blank type class in Haskell (where blank is true when empty, False, or a String of only whitespace). I started, simply, as one might with:

class Blank a where
  blank :: a -> Bool

instance Blank Bool where
  blank = not

instance Blank [a] where
  blank = null

When I went to implement the String instance:

instance Blank String where
  blank [] = True
  blank (' ':xs) = blank xs
  blank ('\t':xs) = blank xs
  blank ('\n':xs) = blank xs
  blank ('\r':xs) = blank xs
  blank _ = False

I was greeted with the error:

Illegal instance declaration for `Blank String'
  (All instance types must be of the form (T t1 ... tn)
   where T is not a synonym.
   Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Blank String'

I'm pretty sure it's because [a] and String are redundant, i.e. String ([Char]) is a more specific instance of [a].

Is there a way get around this and define a more specific instance (i.e. [Char])?

Upvotes: 4

Views: 294

Answers (1)

Bakuriu
Bakuriu

Reputation: 101979

There's a standard trick to achieve what you want in an elegant way. If you think about what you want to do you should remember one standard and very used class that uses this: Show.

Maybe you never thought about how show [1,2,3] == "[1, 2, 3]" while show "hello" == "\"hello\"", yet the problem is the same. The way in which this is resolved is by adding a showList :: [a] -> ShowS operation to the Show class, adding a default definition for it, and then provide an instance for generic lists.

In your case you want something like:

import Data.Char(isSpace)

class Blank a where
  blank :: a -> Bool
  blankList :: [a] -> Bool
  blankList = null

instance Blank a => Blank [a] where
  blank = blankList

instance Blank Char where
  blank = isSpace

  blankList [] = True
  blankList (' ':xs) = blankList xs
  blankList ('\t':xs) = blankList xs
  blankList ('\n':xs) = blankList xs
  blankList ('\r':xs) = blankList xs
  blankList _ = False

In this way whenever you call blank over a list the corresponding blankList method is called. The method used is the one provided by the instance of the elements of the list, and hence the Char instance can define blankList to return True when the list is non-empty but contains only spaces etc while other instances can simply use the default implementation of null.

The downside of this implementation is that you have to put the operations for Strings into the instance for Char. However there is no way around it: in Haskell 98/2010 you can have only one instance for type constructor and class so once you define a Blank [a] instance you cannot have other instances using [], which include String.

Replacing the instance for Blank [a] with an instance for Blank a => Blank [a] allows you to define only one instance for [] but its behaviour changes depending on the instance for Blank a and thus you can special case String.


An alternative solution is to simply not handle Strings at all, as already said. You can always define:

newtype MyString = MyString String

and then use MyString in place of String.

Upvotes: 1

Related Questions