سؤال

ليس من النادر أن يرغب المرء في تنفيذ <=> (مقارنة، أو "سفينة الفضاء") على نوع بيانات المنتج، أي فئة تحتوي على حقول متعددة (كلها (نأمل!) لديها بالفعل <=> تم تنفيذها)، مقارنة الحقول بترتيب معين.

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

يعد هذا أمرًا مملًا وعرضة للخطأ، خاصة مع وجود الكثير من المجالات.إنها عرضة للخطأ بدرجة كافية لدرجة أنني أشعر في كثير من الأحيان أنني يجب أن أقوم باختبار هذه الوظيفة، مما يزيد من الملل والإسهاب.

يقدم هاسكل طريقة لطيفة بشكل خاص للقيام بذلك:

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]

إذن الأسئلة:

  1. ما هي الطريقة النموذجية لتنفيذ هذا النوع من الأشياء في روبي؟
  2. ما هي أفضل طريقة للقيام بذلك باستخدام ما هو محدد في المكتبات القياسية؟
  3. إلى أي مدى يمكن للمرء الوصول إلى كود هاسكل أعلاه، وما مدى موثوقيته بالمقارنة؟إذا لزم الأمر، كيف يمكن للمرء التأكد من أن الحقول قد تم تنفيذها بشكل صحيح <=> أو < و > العاملين؟

بالمناسبة، على الرغم من أن هذا سؤال يتعلق بـ Ruby، إلا أنه يسعدني التفكير في مناقشة تقنيات هاسكل حول الموضوع إذا وافق كبار هذا الموقع على ذلك.لا تتردد في التعليق على ما إذا كان ذلك مناسبًا أم لا، وإذا كان كذلك، ضع علامة على هذا المنشور بـ "haskell" أيضًا.

هل كانت مفيدة؟

المحلول

وهنا حثالة على فكرتك.لا يحدد أي ثوابت إضافية، ويسمح لك باستخدام أي مجموعة من متغيرات الحالة والأساليب لمقارنة كائنين، وله خروج مبكر عند عدم المساواة، ويتضمن جميع الأساليب المحددة بواسطة 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

إذا أردت ، يمكنك إدراج بعض الأخطاء الإضافية في التحقق هناك للتأكد من أن القيم المستخدمة لمقارنة الاستجابة بالفعل <=> (استخدام respond_to? '<=>') ، وحاول إعطاء رسائل خطأ أوضح في الحالة التي لا يفعلونها.

نصائح أخرى

إليك ما أفعله لجعل قواعد الفرز المخصصة أكثر قابلية للإدارة:في جميع فصولي التي أحتاج إلى فرزها، أقوم بتحديد أساليب "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 الخاصة بالنوع وهذا التجاوز <=> نفسه) ينتقل داخليًا لفرز مصفوفة من المصفوفات.

لقد اتخذت نهجا مماثلا مثل rampion، ولكن أراد التعامل مع الحالة التي يمكن أن تكون فيها السمات 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