質問

めったにないことですが、 <=> 積データ型の (比較、または「宇宙船」) 演算子、つまり、複数のフィールドを持つクラス (そのすべてが (私たちが望んでいます!) すでに持っているものです) <=> 実装されています)、フィールドを特定の順序で比較します。

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

これは 2 つの関数に適用できる関数を生成します。 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」というタグを付けてください。

役に立ちましたか?

解決

ここでは、あなたのアイデアのリフです。それは、あなたが2つのオブジェクトを比較するために、インスタンス変数やメソッドの任意の組み合わせを使用することができます - 等しくないの早期終了を持って、余分な定数を定義し、同等で定義されたすべてのメソッドが含まれません。

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
あなたが望んでいた場合は、

、あなたはそれを確認するためにそこにチェックをいくつかの余分なエラーを挿入することができ 比較するために使用される値は、実際に(<=>を使用して)respond_to? '<=>'への対応、およびにしてみてください そうではないwwhere場合の明確なエラーメッセージを与えます。

他のヒント

は、ここで私は、カスタムの並べ替えルールを管理しやすくするために何をすべきかです:私が今までにソートする必要があるすべての私のクラスには、私はto_sortを使用する<=>オーバーライドし、その後配列を返す「to_sort」メソッドを定義し、かつます:

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

  def <=>(o)
    self.to_sort <=> o.to_sort
  end
end

このようだけ配列の配列をソートする内部デボルブ(タイプ固有to_sortの機能を実現すべてがWhateversとWhateverothersとWhathaveyoursの異種配列、およびこの同じ<=>オーバーライドを含む)Whateversの任意の配列をソートします。

ランピオンと同じようなアプローチをとりましたが、 属性が次のような場合に対処したいと考えていました。 nil.

module ComparableBy
  def comparable_by(*attributes)
    include Comparable

    define_method(:<=>) do |other|
      return if other.nil?
      attributes.each do |attribute|
        left  = self.__send__(attribute)
        right = other.__send__(attribute)
        return -1 if left.nil?
        return 1 if right.nil?
        comparison = left <=> right
        return comparison unless comparison == 0
      end
      return 0
    end
  end
end

使用例:

SomeObject = Struct.new(:a, :b, :c) do
  extend ComparableBy
  comparable_by :a, :b, :c
end

さて、ここでは合理的に良い方法であると思われるものの中でこれを実現するためにObjectにする拡張機能で簡単にハックだ。

class Object

    def self.spaceship_uses(*methods)
        self.const_set(:SPACESHIP_USES, methods)
    end

    def <=>(o)
        raise(NoMethodError, "undefined method `<=>' for #{self.inspect}") \
            unless self.class.const_defined?(:SPACESHIP_USES)
        self.class.const_get(:SPACESHIP_USES).each { |sym|
            self.send(sym) < o.send(sym) && (return -1)
            self.send(sym) > o.send(sym) && (return  1)
        }
        return 0
    end

end

class T

    def initialize(f1, f2) @f1, @f2 = f1, f2; end

    attr_reader    :f1, :f2
    spaceship_uses :f1, :f2

end

これはもちろん、<>が正しくSPACESHIP_USES内のメソッドによって返されたオブジェクトのために実装されていることを確認するために、任意のタイピング問題に対処しません。しかし、その後ゲインは、ルビーであること、これはおそらく大丈夫です、それはないですか?

ショートコメントはこれについてコメントすることができますが、私は他の回答で詳細な議論や拡張を見ることに興味があると思います。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top