[LRUG] Comparable Null Objects
Duncan Stuart
dgmstuart at gmail.com
Wed Jul 15 08:34:57 PDT 2015
Thanks everyone - that was really interesting. Here's the approach I've
decided on:
class NoExpectedDate
include Comparable
def to_s(format)
"Unknown"
end
def <=>(other)
1 # Treat it as after every other date
end
def coerce(other)
[other, Float::INFINITY]
end
end
I like it because it has just enough behaviour to quack like a Date where
required.
pry(main)> NoExpectedDate.new > NoExpectedDate.new
=> true
pry(main)> NoExpectedDate.new < NoExpectedDate.new
=> false
pry(main)> Date.today < NoExpectedDate.new
=> true
pry(main)> Date.today > NoExpectedDate.new
=> false
On 15 July 2015 at 16:34, Duncan Stuart <dgmstuart at gmail.com> wrote:
> Thanks John
>
> Yeah - that's pretty much what I suggested at the end of my original post.
> The reason I wanted to try and find another way is because it has that
> typecheck on expected_date, which seems to negate the benefits of using a
> renderable NullObject in the first place.
>
> I feel like Tim's approach of events.sort_by { |event|
> event.expected_date || Float::INFINITY } would be preferable if dealing
> with it at the Event level.
>
>
> On 15 July 2015 at 16:16, <jc at panagile.com> wrote:
>
>> 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
>>>>
>>>>
>>>
>>
>> _______________________________________________
>> 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/298af083/attachment-0003.html>
More information about the Chat
mailing list