我试图实现一个排序的多属性排序,可以在任何列表上运行。
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
。
我已经阅读了一些关于存在类型和异类列表的内容,但我不确定如何继续。
答案 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