Reputation: 8947
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
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 String
s 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 String
s at all, as already said. You can always define:
newtype MyString = MyString String
and then use MyString
in place of String
.
Upvotes: 1