简体   繁体   中英

Creating a new Ord instance for Lists

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

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 (eg 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 ab 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).

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 eg ZipList or several monoids like Sum and Product )

Now you can easily reuse the instances for list, and you can use sum as well.

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 (eg 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.

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?

.. 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM