Reputation: 6778
This is my first attempt at creating a custom instance of a class such as Ord.
I've defined a new data structure to represent a list:
data List a = Empty | Cons a (List a)
deriving (Show, Eq)
Now I want to define a new instance of Ord for List such that List a <= List b implies "the sum of the elements in List a is less than or equal to the sum of the elements in List b"
first of all, is it necessary to define a new "sum" function since the sum defined in Prelude wont work with the new List data type? then, how do I define the new instance of Ord for List?
thanks
Upvotes: 5
Views: 2864
Reputation: 501
.. or another solution with the sum function in Prelude.
data List a = Empty | Cons a (List a)
deriving (Show, Eq)
instance (Ord a, Num a, Eq a) => Ord (List a) where
-- 2 empty lists
Empty <= Empty = True
-- 1 empty list and 1 non-empty list
Cons a b <= Empty = False
Empty <= Cons a b = True
-- 2 non-empty lists
Cons a b <= Cons c d = sum (listToList (Cons a b))
<= sum (listToList (Cons c d))
-- convert new List to old one
listToList :: (Num a) => List a -> [a]
listToList Empty = []
listToList (Cons a rest) = [a] ++ listToList rest
Upvotes: 0
Reputation: 501
What about ..
data List a = Empty | Cons a (List a)
deriving (Show, Eq)
instance (Ord a, Num a, Eq a) => Ord (List a) where
-- 2 empty lists
Empty <= Empty = True
-- 1 empty list and 1 non-empty list
Cons a b <= Empty = False
Empty <= Cons a b = True
-- 2 non-empty lists
Cons a b <= Cons c d = sumList (Cons a b) <= sumList (Cons c d)
-- sum all numbers in list
sumList :: (Num a) => List a -> a
sumList Empty = 0
sumList (Cons n rest) = n + sumList rest
Is this what you are looking for?
Upvotes: 0
Reputation: 54574
How about...
newtype List a = List [a]
This is very common if you want to introduce new, "incompatible" type class instances for a given type (see e.g. ZipList
or several monoids like Sum
and Product
)
Now you can easily reuse the instances for list, and you can use sum
as well.
Upvotes: 4
Reputation: 20950
Generalizing @Tikhon's approach a bit, you could also use Monoid
instead of Num
as constraint, where you already have a predefined "sum" with mconcat
(of course, you still needed Ord
). This would give you some more types into consideration than just numbers (e.g. List (List a)
, which you can now easily define recursively)
On the other hand, if you do want to use a Num
as monoid, you have to decide every time for Sum
or Product
. It could be argued, that having to write this out explicitly can reduce the shortness and readability, but it's a design choice that depends on what degree of generality you want to have in the end.
Upvotes: 2
Reputation: 68152
First of all, this won't work exactly like the normal list instance. The normal instance only depends on the items of the list being orderable themselves; your proposal depends on their being numbers (e.g. in the Num
class) and so is more narrow.
It is necessary to define a new sum
function. Happily it's very easy to write sum
as a simple recursive function. (Coincidentally, you can call your function sum'
, which is pronounced as "sum prime" and by convention means it's a function very similar to sum
.)
Additionally, the instance would have to depend on the Num
class as well as the Ord
class.
Once you have a new sum
function, you can define an instance something like this:
instance (Ord n, Num n) => Ord (List n) where compare = ...
-- The definition uses sum'
This instance statement can be read as saying that for all types n
, if n
is in Ord
and Num
, List n
is in Ord
where comparisons work as follows. The syntax is very similar to math where =>
is implication. Hopefully this makes remembering the syntax easier.
You have to give a reasonable definition of compare
. For reference, compare a b
works as follows: if a < b
it returns LT
, if a = b
it returns EQ
and if a > b
it returns GT
.
This is an easy function to implement, so I'll leave it as an exercise to the reader. (I've always wanted to say that :P).
Upvotes: 13