تنفيذ روبي <=> Combinator
-
22-08-2019 - |
سؤال
ليس من النادر أن يرغب المرء في تنفيذ <=>
(مقارنة، أو "سفينة الفضاء") على نوع بيانات المنتج، أي فئة تحتوي على حقول متعددة (كلها (نأمل!) لديها بالفعل <=>
تم تنفيذها)، مقارنة الحقول بترتيب معين.
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]
إذن الأسئلة:
- ما هي الطريقة النموذجية لتنفيذ هذا النوع من الأشياء في روبي؟
- ما هي أفضل طريقة للقيام بذلك باستخدام ما هو محدد في المكتبات القياسية؟
- إلى أي مدى يمكن للمرء الوصول إلى كود هاسكل أعلاه، وما مدى موثوقيته بالمقارنة؟إذا لزم الأمر، كيف يمكن للمرء التأكد من أن الحقول قد تم تنفيذها بشكل صحيح
<=>
أو<
و>
العاملين؟
بالمناسبة، على الرغم من أن هذا سؤال يتعلق بـ 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
.ولكن بعد ذلك، كونك روبي، ربما يكون هذا أمرًا جيدًا، أليس كذلك؟
يمكن للتعليقات القصيرة التعليق على هذا، ولكنني سأكون مهتمًا برؤية مناقشة تفصيلية وإضافات في الإجابات الأخرى.