[LRUG] Comparable Null Objects

jc at panagile.com jc at panagile.com
Wed Jul 15 08:16:10 PDT 2015


Hmm, if you’re sorting a list of events then why not let the event’s control how the comparison works? E.g., something like:





require 'date'




class NoExpectedDate

end




class Event

  attr_reader :expected_date




  def initialize(date)

    @expected_date = date

  end




  def unsortable?

    expected_date.is_a?(NoExpectedDate)

  end




  def <=>(other)

    if self.unsortable?

      1

    elsif other.unsortable?

      -1

    else

      self.expected_date <=> other.expected_date

    end

  end

end







e1 = Event.new(Date.today)

e2 = Event.new(NoExpectedDate.new)

e3 = Event.new(Date.today - 1)




[e1, e2, e3].sort # => [#<Event:0x007f968511a3c8 @expected_date=#<Date: 2015-07-14 ((2457218j,0s,0n),+0s,2299161j)>>, #<Event:0x007f968511a490 @expected_date=#<Date: 2015-07-15 ((2457219j,0s,0n),+0s,2299161j)>>, #<Event:0x007f968511a440 @expected_date=#<NoExpectedDate:0x007f968511a468>>]









This is still pretty imperative, but that’s probably a good thing in this case. The pure OO approach to this gets nasty quickly.

On Wed, Jul 15, 2015 at 3:54 PM, Duncan Stuart <dgmstuart at gmail.com>
wrote:

> Thanks John
> * I want to sort things of the same type (instances of the Event class).
> * I want to sort them *by* an attribute containing things which Quack
> enough like dates to be sorted against each other
> * I want to do this so that they're displayed in date order in an email
> * Yes, it's an Array. I could create an EmailEventCollection object which I
> would tell "give me the things for my email" and it would respond with an
> array which was (amongst other things) sorted, but I don't think that would
> resolve the issue at hand: it hides the mess in a different way, but
> internally I'd still be doing the same sort, no?
> On 15 July 2015 at 15:09, <jc at panagile.com> wrote:
>>
>> | Some events don't have an expected date, so like a good little OO
>> programmer i've created a Null object:
>>
>> Yay! Good OO programmer.
>>
>> | but when it comes to sorting the list
>>
>> wait, wat? Less good OO programmer.
>>
>> I can see three things going on here, pretty much simultaneously.
>>
>> 1) You want to sort a list of different types of things. Like when you try
>> to sort a list of Silverback Gorillas and instances of the number 37 it’s
>> hard because it doesn’t really make sense.
>>
>> 2) You don’t have a list. You have an Array object. Is this the best place
>> to send the ‘sort’ message to? Should it own the behaviour of sorting these
>> particular disparate types of things?
>>
>> 3) You switched from clean OO thinking (we need an object to encapsulate
>> this state) to imperative thinking (we have a list and we need to sort it).
>>
>> I blame Ruby for all of this. It makes you think you’re doing OO
>> programming but throws in many non-OO ideas to trip you up.
>>
>> It’s not clear that an OO approach is the best approach here, but if you
>> want to follow it try to think less about objects and more about the
>> messages between objects. E.g., instead of ‘I need to sort a list’ think ‘I
>> need to send a message somewhere telling it to do something (in this case
>> to… what? To sort some objects? But why do you care?)’.
>>
>> Hope this helps or at least makes you think or at the very least persuades
>> you to rage quit programming and go and live in a forest,
>> John
>>
>>
>> On Wednesday, Jul 15, 2015 at 1:03 pm, Duncan Stuart <dgmstuart at gmail.com>,
>> wrote:
>>
>>> Hi LRUG - hopefully an interesting little problem:
>>>
>>> I have an Event class which has an "expected_date" attribute.
>>> Some events don't have an expected date, so like a good little OO
>>> programmer i've created a Null object:
>>>
>>> class NoExpectedDate
>>>   def to_s(format=:default)
>>>     "Unknown"
>>>   end
>>> end
>>>
>>> This works great for printing the values, but when it comes to sorting
>>> the list I of course get:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>    ArgumentError: comparison of Date with NoExpectedDate failed
>>>
>>> If I include Comparable and define <=> then one comparison works, but the
>>> other doesn't :
>>>
>>> class NoExpectedDate
>>>   include Comparable
>>>   def to_s(format=:default)
>>>     "Unknown"
>>>   end
>>>   def <=>(other_date)
>>>     1 # Treat it as after every other date
>>>   end
>>> end
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>> $ NoExpectedDate.new > Date.today
>>> => true
>>>
>>>
>>> $ Date.today > NoExpectedDate.new
>>> ArgumentError: comparison of Date with NoExpectedDate failed
>>>
>>> I think this is because Date's <=> method expects it's argument to be a
>>> Date object or a number ("a numeric value as an astronomical Julian day
>>> number"). I've tried defining to_i and to_r on NoExpectedDate, but no dice.
>>>
>>> Can I get NoExpectedDate to pretend to be a Date (like SimpleDelegator
>>> lies about it's class)? Is that evil?
>>>
>>> I suppose I could always just define a method on Event to do this
>>> particular sort, but that seems nasty for all sorts of reasons:
>>>
>>> def sort_by_expected_date sort do |a, b|
>>>
>>>     if b.class = NoExpectedDate
>>>
>>>       1
>>>
>>>     else
>>>
>>>       a <=> b
>>>
>>>     end
>>>
>>>   end
>>>
>>> end
>>>
>>>
>>>
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20150715/96ff6dad/attachment.html>


More information about the Chat mailing list