Создание нового экземпляра Ord для списков

Это моя первая попытка создать собственный экземпляр класса, такого как Ord.

Я определил новую структуру данных для представления списка:

data List a = Empty | Cons a (List a)
    deriving (Show, Eq)

Теперь я хочу определить новый экземпляр Ord для List, такой что List a ‹= List b подразумевает, что« сумма элементов в List a меньше или равна сумме элементов в List b »

Прежде всего, необходимо ли определять новую функцию «суммы», поскольку сумма, определенная в Prelude, не будет работать с новым типом данных List? тогда как мне определить новый экземпляр Ord для List?

Благодарность


person cdk    schedule 25.05.2012    source источник


Ответы (5)


Во-первых, это не будет работать точно так же, как обычный экземпляр списка. Обычный экземпляр зависит только от того, что элементы списка сами заказываются; ваше предложение зависит от их числа (например, в классе Num) и поэтому является более узким.

Необходимо определить новую sum функцию. К счастью, очень легко написать sum как простую рекурсивную функцию. (По совпадению, вы можете вызвать свою функцию sum', которая произносится как «сумма простых чисел» и по соглашению означает, что это функция, очень похожая на sum.)

Кроме того, экземпляр должен зависеть как от класса Num, так и от класса Ord.

Когда у вас есть новая функция sum, вы можете определить экземпляр примерно так:

instance (Ord n, Num n) => Ord (List n) where compare = ... 
  -- The definition uses sum'

Этот оператор экземпляра может быть прочитан как говорящий, что для всех типов n, если n находится в Ord и Num, List n находится в Ord, где сравнения работают следующим образом. Синтаксис очень похож на математический, где => подразумевается. Надеюсь, это облегчит запоминание синтаксиса.

Вы должны дать разумное определение compare. Для справки, compare a b работает следующим образом: если a < b, он возвращает LT, если a = b, он возвращает EQ, а если a > b, он возвращает GT.

Эту функцию легко реализовать, поэтому я оставлю ее читателю в качестве упражнения. (Я всегда хотел сказать это: P).

person Tikhon Jelvis    schedule 25.05.2012
comment
отличный ответ, я бы поддержал, если бы мог ... Haskell удивительно интуитивно понятен, если у вас есть ответ перед вами - person cdk; 25.05.2012

Как насчет...

newtype List a = List [a]

Это очень часто, если вы хотите ввести новые, «несовместимые» экземпляры класса типа для данного типа (см., Например, ZipList или несколько моноидов, таких как Sum и Product)

Теперь вы можете легко повторно использовать экземпляры для списка, а также использовать sum.

person Landei    schedule 25.05.2012

Немного обобщая подход @Tikhon, вы также можете использовать Monoid вместо Num в качестве ограничения, где у вас уже есть предопределенная «сумма» с mconcat (конечно, вам все еще нужно Ord). Это даст вам во внимание еще несколько типов, чем просто числа (например, List (List a), который теперь вы можете легко определить рекурсивно)

С другой стороны, если вы действительно хотите использовать Num в качестве моноида, вы должны каждый раз выбирать Sum или Product. Можно было бы возразить, что необходимость написания этого явно может уменьшить краткость и удобочитаемость, но это выбор дизайна, который зависит от того, какую степень универсальности вы хотите иметь в конечном итоге.

person phipsgabler    schedule 25.05.2012
comment
кстати, вот Data.Monoid - person phipsgabler; 25.05.2012

Как насчет ..

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

Это то, что вы ищите?

person jimmyt    schedule 25.05.2012

.. или другое решение с функцией суммы в 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
person jimmyt    schedule 25.05.2012