人们常常希望在产品数据类型上实现 <=>
(比较或“太空飞船”)运算符,即具有多个字段的类(所有这些字段(我们希望!) ) 已经实现了 <=>
),按特定顺序比较字段。
def <=>(o)
f1 < o.f1 && (return -1)
f1 > o.f1 && (return 1)
f2 < o.f2 && (return -1)
f2 > o.f2 && (return 1)
return 0
end
这既乏味又容易出错,尤其是对于很多字段。 它很容易出错,以至于我经常觉得应该对该函数进行单元测试,这只会增加乏味和冗长。
Haskell 提供了一种特别好的方法来做到这一点:(
import Data.Monoid (mappend)
import Data.Ord (comparing)
-- From the standard library:
-- data Ordering = LT | EQ | GT
data D = D { f3 :: Int, f2 :: Double, f1 :: Char } deriving Show
compareD :: D -> D -> Ordering
compareD = foldl1 mappend [comparing f1, comparing f2, comparing f3]
对于那些不熟悉 fold
的人,上面的内容扩展为
comparing f1 `mappend` comparing f2 `mappend` comparing f3
生成一个可以应用于两个 D
的函数,生成一个 Ordering
。)
compareD
的定义非常简单,它显然是正确的,即使没有静态类型检查,我也不觉得需要对其进行单元测试。
实际上,这个问题可能比这更有趣,因为我可能不想只使用标准 <=>
运算符,而是在不同时间以不同方式排序,例如:
sortByOrderings :: [a -> a -> Ordering] -> [a] -> [a]
sortByOrderings = sortBy . foldl1 mappend
sortByF3F1 = sortByOrderings [comparing f3, comparing f1]
sortByF2F3 = sortByOrderings [comparing f2, comparing f3]
所以,问题:
- 在 Ruby 中实现此类事情的典型方法是什么?
- 使用标准库中定义的内容最好的方法是什么?
- 相比之下,我们可以与上面的 Haskell 代码有多接近,以及它的可靠性如何? 如果需要,如何确保字段具有正确实现的
<=>
或 <
和 >
运算符?
顺便说一句,虽然这是一个 Ruby 问题,但如果本网站的前辈们同意的话,我很乐意考虑对 Haskell 技术进行主题讨论。 请随意评论这是否合适,如果合适,也请将此帖子标记为“haskell”。
Not infrequently, one wants to implement the <=>
(comparison, or "spaceship") operator on a product data type, i.e., a class with multiple fields (all of which (we hope!) already have <=>
implemented), comparing the fields in a certain order.
def <=>(o)
f1 < o.f1 && (return -1)
f1 > o.f1 && (return 1)
f2 < o.f2 && (return -1)
f2 > o.f2 && (return 1)
return 0
end
This is both tedious and error-prone, especially with a lot of fields. It's error-prone enough that I frequently feel I should unit test that function, which just adds to the tediousness and verbosity.
Haskell offers a particularly nice way of doing this:
import Data.Monoid (mappend)
import Data.Ord (comparing)
-- From the standard library:
-- data Ordering = LT | EQ | GT
data D = D { f3 :: Int, f2 :: Double, f1 :: Char } deriving Show
compareD :: D -> D -> Ordering
compareD = foldl1 mappend [comparing f1, comparing f2, comparing f3]
(For those not familiar with fold
, the above expands to
comparing f1 `mappend` comparing f2 `mappend` comparing f3
which produces a function that can be applied to two D
s, to produce an Ordering
.)
The defintion of compareD
is so simple that it's obviously correct, and I wouldn't feel the need to unit test it even without static type checking.
Actually, the question may be even slightly more interesting than this, since I may not want to use just the standard <=>
operator, but sort in different ways at different times, e.g.:
sortByOrderings :: [a -> a -> Ordering] -> [a] -> [a]
sortByOrderings = sortBy . foldl1 mappend
sortByF3F1 = sortByOrderings [comparing f3, comparing f1]
sortByF2F3 = sortByOrderings [comparing f2, comparing f3]
So, the questions:
- What's the typical way of implementing this sort of thing in Ruby?
- What's the nicest way of doing it using just what's defined in the standard libraries?
- How close can one get to the Haskell code above, and how reliable is it, in comparison? If necessary, how can one ensure that the fields have a properly implemented
<=>
or <
and >
operators?
Incidently, while this is a Ruby question, I'm happy to consider discussion of the Haskell techniques on-topic if the elders of this site so agree. Please feel free to comment on whether that's appropriate or not and, if it is, tag this post 'haskell' as well.
发布评论
评论(4)
为了使自定义排序规则更易于管理,我做了以下操作:在我需要排序的所有类上,我定义返回数组的“to_sort”方法,然后覆盖 <=>; 使用 to_sort:
因此,对任何 Whats 数组进行排序(包括 Whatvers 和 Whathaveothers 和 Whathaveyours 的异构数组,所有这些都实现特定于类型的 to_sort 函数和相同的 <=> 覆盖)只是在内部转移到对数组数组进行排序。
Here's what I do to make custom sorting rules more manageable: on all my classes I ever need to sort, I define "to_sort" methods that return arrays, and then override <=> to use to_sort:
Thus sorting any array of Whatevers (including heterogeneous arrays of Whatevers and Whateverothers and Whathaveyours, all of which implement type-specific to_sort functions and this same <=> override) just devolves internally to sorting an array of arrays.
这是你的想法的即兴表演。 它没有定义任何额外的常量,允许您使用实例变量和方法的任意组合来比较两个对象,在不等于时提前退出,并包含 Comparable 定义的所有方法。
如果您愿意,您可以在其中插入一些额外的错误检查以确保
用于比较的值实际上响应
<=>
(使用respond_to? '<=>'
),并尝试在没有提供的情况下给出更清晰的错误消息。
Here's a riff on your idea. It doesn't define any extra constants, allows you to use any combination of instance variables and methods to compare two objects, has early exit on not-equal, and includes all the methods defined by Comparable.
If you wanted, you could insert some extra error checking in there to make sure that
the values used to compare actually respond to
<=>
(usingrespond_to? '<=>'
), and try togive clearer error messages in the case wwhere they don't.
我采用了与 Rampion 类似的方法,但想要处理属性可能
nil
的情况。示例用法:
I took a similar approach as rampion, but wanted to handle the case where attributes could be
nil
.Example Usage:
好吧,这里有一个对
Object
扩展的快速破解,以一种看起来相当不错的方式实现这一点。这当然不会处理任何类型问题,以确保为
SPACESHIP_USES 中的方法返回的对象正确实现
。 但作为 Ruby,这可能没问题,不是吗?<
和>
简短的评论可以对此发表评论,但我有兴趣在其他答案中看到详细的讨论和扩展。
Well, here's a quick hack at an extension to
Object
to make this happen in what seems to be a reasonably nice way.This of course doesn't deal with any typing issues, to make sure that
<
and>
are properly implemented for the objects returned by the methods inSPACESHIP_USES
. But then gain, being Ruby, this is probably fine, isn't it?Short comments can comment on this, but I'd be interested in seeing detailed discussion and extensions in other answers.