Haskell:多属性排序不够通用

时间:2016-05-21 13:56:51

标签: sorting haskell generics

我试图实现一个排序的多属性排序,可以在任何列表上运行。

import Data.Ord (Ordering, Down (..), comparing)
import Data.List (sortBy)
import Data.Monoid (mconcat)

data Order a = ASC a | DESC a

orderBy :: Ord b => [Order (a -> b)] -> [a] -> [a]
orderBy rankedProperties unsorted = 
    sortBy rankedCompare unsorted 
    where 
        rankedCompare x y = 
            mconcat $ map 
                (\property -> 
                    case property of 
                        ASC  f -> comparing f x y
                        DESC f -> comparing (Down . f) x y
                ) rankedProperties

它现在适用于元组和记录,但是我发现了一个问题。问题是b中的orderBy必须相同。这是考虑到这个:

data Row = Row { shortListed :: Bool, cost :: Float, distance1 :: Int, distance2 :: Int } deriving (Show, Eq)

我希望能够说:orderBy [ASC shortListed, DESC cost] listofrows

但回来的错误是:

<interactive>:1:31:
    Couldn't match type ‘Float’ with ‘Bool’
    Expected type: Row -> Bool
      Actual type: Row -> Float
    In the first argument of ‘ASC’, namely ‘cost’
    In the expression: ASC cost

我需要一种方法来使b类型具有通用性,因为b函数comparing只能真正接受comparing :: Ord a => (b -> a) -> b -> b -> Ordering

我已经阅读了一些关于存在类型和异类列表的内容,但我不确定如何继续。

1 个答案:

答案 0 :(得分:5)

由于Monoid Ordering中有instance Monoid b => Monoid (a -> b)Prelude,因此我们通过迭代函数实例两次来获得Monoid (a -> a -> Ordering)。这让我们可以非常简单地解决问题,而不会存在:

import Data.Ord (Ordering, comparing)
import Data.List (sortBy)
import Data.Monoid ((<>), mconcat)

data Row = Row {
  shortListed :: Bool,
  cost :: Float,
  distance1 :: Int,
  distance2 :: Int
  } deriving (Show, Eq)

asc, desc :: Ord b => (a -> b) -> a -> a -> Ordering
asc  = comparing
desc = flip . asc

list :: [Row]
list = [Row False 0 10 20, Row True 10 30 40]

list' :: [Row]
list' = sortBy (asc shortListed <> desc cost <> asc distance1) list

可替换地:

orderBy :: [a -> a -> Ordering] -> [a] -> [a]
orderBy = sortBy . mconcat

list'' :: [Row]
list'' = orderBy [asc shortListed, desc cost, asc distance1] list