<div dir="ltr">Have I totally misunderstood the point of the Null Object pattern? This is all still fairly new to me - but here's a description that sums up what I'm basing my understanding on:<br>"The Null Object provides intelligent do nothing behavior, hiding the details from its collaborators."<br><a href="http://www.cs.oberlin.edu/~jwalker/nullObjPattern/">http://www.cs.oberlin.edu/~jwalker/nullObjPattern/</a><br><br>My instinct to use it here was that there's a set of behaviours which I want to hide from the Event class: those involved in handling the case where there is no expected_date. <br><br>I agree that the code isn't very clear, but I'd rather hide that (probably quite stable?) mess within a separate object than have it pollute the Event class. <br><br>Are these not sensible goals?<br><br>I can see how making an <span style="font-size:12.8000001907349px">EventWithoutExpectedDate class would also fulfil that mess-hiding role, but say in a different scenario I needed to also sort by venue name, and handle a case where a Venue is missing from the event: I'd pretty much *have* to use composition at that point - and wouldn't that inevitably involve injecting NoExpectedDate and UnknownVenue null objects anyway? Is there another way to achieve it?<br></span><br></div><div class="gmail_extra"><br><div class="gmail_quote">On 15 July 2015 at 18:20, <span dir="ltr"><<a href="mailto:jc@panagile.com" target="_blank">jc@panagile.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<span><div>I don’t really understand why type coercion is OK but type checking is bad. At least with type checking your code spells out what you want to check - with coercion a type check is still happening, it’s just that it’s happening implicitly in the ruby vm and not explicitly in your code. It’s also rather non-obvious - at least to me - that comparing a Date to a NoExpectedDate relies on ‘def coerce(other); [other, Float::INFINITY]; end’. It’s too clever for me.</div>
<div><br></div>
<div>I still think you’re getting the wrong thing to Quack. You don’t really want Dates to be comparable to NoExpectedDate. It doesn’t make sense. You’re just using some clever implementation details to return the incidentally correct answer from what you really want - the comparison of events.</div>
<div><br></div>
<div>If you really want to avoid type checking then presumably you could set some attribute in an Event when you’re setting the expected_date to NoExpectedDate. Then you could check that attribute rather than checking the class of expected_date. But I don’t think that would buy you much.</div>
<div><br></div>
<div>If you really wanted to avoid the imperative style ‘if/else’ then I think that the better solution is to specialise Event, not to create a null object for one of it’s attributes. E.g., you could create an EventWithoutDate class (probably as a subclass of Event*). Then add a method called something like ‘value_for_sort’ that returns the expected_date (perhaps .to_f) for Event, and Float::INFINITY for EventWithoutDate, and use these in the comparison. But that seems like a lot of work and it still relies on a special value (Float::INFINITY) to represent some behaviour specific to the ordering of events. I don’t think it would be a better solution.</div>
<div><br></div>
<div>I still think the clearest solution in this case is to use an if/else. I don’t think that you should have created a NoExpectedDate object. One reason for this is that you don’t own Date so you can’t make it do the right thing in this case. The other reason is that you’re not trying to sort Dates, so I think you’re being tempted to put the behaviour in the wrong place.</div>
<div><br></div>
<div><br></div>
<div>* although subclassing to specialise one behaviour can be a code smell**</div>
<div>** bonus points for doing it with composition. Why use one class when you can use four and make your code unreadable?</div></span><div class="HOEnZb"><div class="h5"><div><br></div>
<br><br><div class="gmail_quote"><p>On Wed, Jul 15, 2015 at 4:35 PM, Duncan Stuart <span dir="ltr"><<a href="mailto:dgmstuart@gmail.com" target="_blank">dgmstuart@gmail.com</a>></span> wrote:<br></p><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div>
<div dir="ltr">
<div>Thanks everyone - that was really interesting. Here's the approach I've decided on:</div>
<div><br></div>
<div>class NoExpectedDate</div>
<div> include Comparable</div>
<div> def to_s(format)</div>
<div> "Unknown"</div>
<div> end</div>
<div> def <=>(other)</div>
<div> 1 # Treat it as after every other date</div>
<div> end</div>
<div> def coerce(other)</div>
<div> [other, Float::INFINITY]</div>
<div> end</div>
<div>end</div>
<div><br></div>
<div>I like it because it has just enough behaviour to quack like a Date where required. </div>
<div><br></div>
<div>pry(main)> NoExpectedDate.new > NoExpectedDate.new</div>
<div>=> true</div>
<div>pry(main)> NoExpectedDate.new < NoExpectedDate.new</div>
<div>=> false</div>
<div>pry(main)> Date.today < NoExpectedDate.new</div>
<div>=> true</div>
<div>pry(main)> Date.today > NoExpectedDate.new</div>
<div>=> false</div>
</div>
<div class="gmail_extra">
<br><div class="gmail_quote">On 15 July 2015 at 16:34, Duncan Stuart <span dir="ltr"><<a href="mailto:dgmstuart@gmail.com" target="_blank">dgmstuart@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div dir="ltr">Thanks John<br><br>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.<br><br>I feel like Tim's approach of <span style="font-size:12.8000001907349px">events.sort_by { |event| event.expected_date || Float::INFINITY } would be preferable if dealing with it at the Event level. <br><br></span>
</div>
<div><div>
<div class="gmail_extra">
<br><div class="gmail_quote">On 15 July 2015 at 16:16, <span dir="ltr"><<a href="mailto:jc@panagile.com" target="_blank">jc@panagile.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<span><div>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:</div>
<div><br></div>
<div>
<div>require 'date'</div>
<div><br></div>
<div>class NoExpectedDate</div>
<div>end</div>
<div><br></div>
<div>class Event</div>
<div> attr_reader :expected_date</div>
<div><br></div>
<div> def initialize(date)</div>
<div> @expected_date = date</div>
<div> end</div>
<div><br></div>
<div> def unsortable?</div>
<div> expected_date.is_a?(NoExpectedDate)</div>
<div> end</div>
<div><br></div>
<div> def <=>(other)</div>
<div> if self.unsortable?</div>
<div> 1</div>
<div> elsif other.unsortable?</div>
<div> -1</div>
<div> else</div>
<div> self.expected_date <=> other.expected_date</div>
<div> end</div>
<div> end</div>
<div>end</div>
<div><br></div>
<div><br></div>
<div>e1 = Event.new(Date.today)</div>
<div>e2 = Event.new(NoExpectedDate.new)</div>
<div>e3 = Event.new(Date.today - 1)</div>
<div><br></div>
<div>[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>>]</div>
</div>
<div><br></div>
<div><br></div>
<div>This is still pretty imperative, but that’s probably a good thing in this case. The pure OO approach to this gets nasty quickly.</div></span><div><div>
<div><br></div>
<br><br><div class="gmail_quote">
<p>On Wed, Jul 15, 2015 at 3:54 PM, Duncan Stuart <span dir="ltr"><<a href="mailto:dgmstuart@gmail.com" target="_blank">dgmstuart@gmail.com</a>></span> wrote:<br></p>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div dir="ltr">Thanks John<br><br>* I want to sort things of the same type (instances of the Event class).<div>* I want to sort them *by* an attribute containing things which Quack enough like dates to be sorted against each other</div>
<div>* I want to do this so that they're displayed in date order in an email</div>
<div>* 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?<br><div class="gmail_extra">
<br><div class="gmail_quote">On 15 July 2015 at 15:09, <span dir="ltr"><<a href="mailto:jc@panagile.com" target="_blank">jc@panagile.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<u></u>
<div>
<span><span><div><br></div>
<div>| Some events don't have an expected date, so like a good little OO programmer i've created a Null object:</div>
<div><br></div>
</span><div>Yay! Good OO programmer.</div>
<span>
<div><br></div>
<div>| but when it comes to sorting the list</div>
<div><br></div>
</span><div>wait, wat? Less good OO programmer.</div>
<div><br></div>
<div>I can see three things going on here, pretty much simultaneously.</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>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?</div>
<div><br></div>
<div>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).</div>
<div style="display:block"><div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>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?)’.</div>
<div><br></div>
<div>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,</div>
<div>John</div>
<div><br></div>
</div></div>
<div><div>
<br><span style="display:inline">On Wednesday, Jul 15, 2015 at 1:03 pm, Duncan Stuart <<a href="mailto:dgmstuart@gmail.com" target="_blank">dgmstuart@gmail.com</a>>, wrote:<br></span>
</div></div></span><div><div>
<span><blockquote class="gmail_quote"><div><div dir="ltr">Hi LRUG - hopefully an interesting little problem:<br><br>I have an Event class which has an "expected_date" attribute.<br>Some events don't have an expected date, so like a good little OO programmer i've created a Null object:<br><br><div>class NoExpectedDate</div>
<div> def to_s(format=:default)</div>
<div> "Unknown"</div>
<div> end</div>
<div>end</div>
<div><br></div>
<div>This works great for printing the values, but when it comes to sorting the list I of course get:<br><br><br><br><br><br><br><br><br><br><br> ArgumentError: comparison of Date with NoExpectedDate failed <br><br>If I include Comparable and define <=> then one comparison works, but the other doesn't :<br><br><div>class NoExpectedDate</div>
<div> include Comparable</div>
<div> def to_s(format=:default)</div>
<div> "Unknown"</div>
<div> end</div>
<div> def <=>(other_date)</div>
<div> 1 # Treat it as after every other date</div>
<div> end</div>
<div>end</div>
</div>
<div>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><p><span>$ </span><span>NoExpectedDate</span><span>.new > </span><span>Date</span><span>.today<br></span><span>=> </span><span>true</span></p>
<br><br><p><span>$ </span><span>Date</span><span>.today > </span><span>NoExpectedDate</span><span>.new<br></span>ArgumentError: comparison of Date with NoExpectedDate failed<br><br>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.<br><br>Can I get NoExpectedDate to pretend to be a Date (like SimpleDelegator lies about it's class)? Is that evil?<br><br>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:<br><br></p>
<p>def sort_by_expected_date sort do |a, b| </p>
<p> if b.class = NoExpectedDate </p>
<p> 1</p>
<p> else</p>
<p> a <=> b</p>
<p> end</p>
<p> end</p>
<p>end</p>
<p><br></p>
</div>
</div></div></blockquote></span>
</div></div>
</div>
<br>_______________________________________________<br>
Chat mailing list<br><a href="mailto:Chat@lists.lrug.org" target="_blank">Chat@lists.lrug.org</a><br>
Archives: <a href="http://lists.lrug.org/pipermail/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/pipermail/chat-lrug.org</a><br>
Manage your subscription: <a href="http://lists.lrug.org/options.cgi/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/options.cgi/chat-lrug.org</a><br>
List info: <a href="http://lists.lrug.org/listinfo.cgi/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/listinfo.cgi/chat-lrug.org</a><br><br></blockquote>
</div>
<br></div>
</div>
</div></div></blockquote>
</div>
<br></div></div>
<br>_______________________________________________<br>
Chat mailing list<br><a href="mailto:Chat@lists.lrug.org" target="_blank">Chat@lists.lrug.org</a><br>
Archives: <a href="http://lists.lrug.org/pipermail/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/pipermail/chat-lrug.org</a><br>
Manage your subscription: <a href="http://lists.lrug.org/options.cgi/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/options.cgi/chat-lrug.org</a><br>
List info: <a href="http://lists.lrug.org/listinfo.cgi/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/listinfo.cgi/chat-lrug.org</a><br><br></blockquote>
</div>
<br></div>
</div></div>
</blockquote>
</div>
<br></div>
</div></blockquote></div><br></div></div><br>_______________________________________________<br>
Chat mailing list<br>
<a href="mailto:Chat@lists.lrug.org">Chat@lists.lrug.org</a><br>
Archives: <a href="http://lists.lrug.org/pipermail/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/pipermail/chat-lrug.org</a><br>
Manage your subscription: <a href="http://lists.lrug.org/options.cgi/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/options.cgi/chat-lrug.org</a><br>
List info: <a href="http://lists.lrug.org/listinfo.cgi/chat-lrug.org" rel="noreferrer" target="_blank">http://lists.lrug.org/listinfo.cgi/chat-lrug.org</a><br>
<br></blockquote></div><br></div>