Ruby <=>组合器的实现

Cur*_*son 6 ruby combinators spaceship-operator

不经常,人们希望<=>在产品数据类型上实现(比较或"太空船")运算符,即具有多个字段的类(所有这些(我们希望!)已经<=>实现),比较某些字段订购.

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
Run Code Online (Sandbox Code Playgroud)

这既繁琐又容易出错,尤其是在很多领域.它容易出错,我经常觉得我应该对这个功能进行单元测试,这只会增加乏味和冗长.

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
Run Code Online (Sandbox Code Playgroud)

它产生一个可以应用于两个Ds 的函数,以产生一个Ordering.)

定义compareD非常简单,显然是正确的,即使没有静态类型检查,我也不觉得需要进行单元测试.

实际上,问题甚至可能比这更有趣,因为我可能不想仅使用标准<=>运算符,而是在不同时间以不同方式排序,例如:

sortByOrderings :: [a -> a -> Ordering] -> [a] -> [a]
sortByOrderings = sortBy . foldl1 mappend

sortByF3F1 = sortByOrderings [comparing f3, comparing f1]
sortByF2F3 = sortByOrderings [comparing f2, comparing f3]

所以,问题:

  1. 在Ruby中实现这种事情的典型方法是什么?
  2. 使用标准库中定义的内容最好的方法是什么?
  3. 与之相比,上面的Haskell代码有多接近,有多可靠?如果必要的话,怎么能够保证领域有一个正确执行<=><>运营商?

很明显,虽然这是一个Ruby问题,但如果本网站的长老如此同意,我很乐意考虑讨论Haskell技术.请随意评论这是否合适,如果是,则标记此帖子'haskell'.

gle*_*ald 8

以下是我使自定义排序规则更易于管理的方法:在我需要排序的所有类中,我定义了返回数组的"to_sort"方法,然后重写<=>以使用to_sort:

class Whatever
  def to_sort
    [@mainkey,@subkey,@subsubkey]
  end

  def <=>(o)
    self.to_sort <=> o.to_sort
  end
end
Run Code Online (Sandbox Code Playgroud)

因此,排序任何Whatevers数组(包括Whatevers和Whateverothers和Whathaveyours的异构数组,所有这些都实现特定于类型的to_sort函数和同样的<=>覆盖)只是在内部转移到排序数组数组.


ram*_*ion 7

这是你的想法的一个即兴重复.它没有定义任何额外的常量,允许您使用实例变量和方法的任意组合来比较两个对象,提前退出不相等,并包括Comparable定义的所有方法.

class Object
    def self.compare_by(*symbols)
        include Comparable
        dispatchers = symbols.map do |symbol|
          if symbol.to_s =~ /^@/
            lambda { |o| o.instance_variable_get(symbol) }
          else
            lambda { |o| o.__send__(symbol) }
          end
        end
        define_method('<=>') do |other|
          dispatchers.inject(0) do |_,dispatcher|
            comp = dispatcher[self] <=> dispatcher[other]
            break comp if comp != 0
            comp
          end
        end
    end
end

class T
    def initialize(name,f1,f2,f3)
      @name,@f1, @f2, @f3 = name,f1, f2, f3;
    end

    def f1
      puts "checking #@name's f1"
      @f1
    end
    def f3
      puts "checking #@name's f3"
      @f3
    end

    compare_by :f1, :@f2, :f3
end

w = T.new('x',1,1,2)
x = T.new('x',1,2,3)
y = T.new('y',2,3,4)
z = T.new('z',2,3,5)

p w < x   #=> checking x's f1
          #   checking x's f1
          #   true
p x == y  #=> checking x's f1
          #   checking y's f1
          #   false
p y <= z  #=> checking y's f1
          #   checking z's f1
          #   checking y's f3
          #   checking z's f3
          #   true
Run Code Online (Sandbox Code Playgroud)

如果你愿意,你可以在那里插入一些额外的错误检查,以确保用于比较的值实际上响应<=>(使用respond_to? '<=>'),并尝试在它们没有的情况下给出更清晰的错误消息.