Tuesday, 1 November 2016

Ah ballocks

G'day:
I just noticed the context-highlighting thing I've got wasn't cross-referencing on that last article I just did ("PHP: what exactly are we testing?").

As it turns out it's cos my JS files were hosted on cfmldeveloper, and that service has been discontinued ("cflivedead.net").

Blogspot itself didn't / wouldn't / couldn't host JS files last time I checked: I'll check again tomorrow (I can't be arsed, this evening). If it can't host separate files, I guess I'll just in-line it. Bleah.

So anyway, sorry the cross-referencing is bung at the moment. I'll sort it out soon.

Update:

This is kinda fixed. I've inlined the JS needed on every article, but some of the article-specific JS is still not gonna work. Once I come up with a file-based solution, rather than needing to inline it, I'll sort the rest out.

Righto.

--
Adam

PHP: what exactly are we testing?

G'day:
"the rumours of this blog's demise... [etc]". Yeah. I'm still here. More about that in a separate article.

Here's a completely fictitious scenario which bears absolutely no relation to a code review discussion I had at work today. I don't even know why I mention work. This, after all, has nothing to do with my day job. Or my colleagues. I'm just making it all up.

Here's some code I just made up for the sake of discussion:

<?php 

namespace me\adamcameron\someApp\service;

use \me\adamcameron\someApp\exception;

class SomeService {

    public function getTheThingFromTheWebService($id){
        // bunch of stuff elided
        
        try {
            $result = $this->connector->getThing($id);
            
            // bunch of stuff elided
            
            return $finalResult;
        } catch (exception\NotFoundException $e) {
            // bunch of stuff elided
            
            throw new exception\ServiceException("The Thing with ID $id could not be retrieved");
         }
    }
}


Basically we're calling a web service, and the connector we use wraps up all the HTTP bumpf, and with stuff like 404 responses it throws a NotFoundException up to the service tier so that the service doesn't need to concern itself with the fact the connector is dealing with a REST web service. It could just as easily be connecting straight to a DB and the SELECT statement returned zero rows: that's still a NotFoundException situation. That's cool. We're all-good with this approach to things. For reasons that are outwith this discussion (TBH, I'm slightly contriving the situation in the code above, but maintaining the correct context) we don't want to let the NotFoundException bubble any further, we want to throw a different exception (one that other upstream code is waiting to catch), and provide a human-friendly message with said exception.

But for testing, I need to actually make sure that the Service handles this exception properly, and the two elements of this are that it chucks its own exception, and it includes that human-friendly message when it does.

So I've tested both of those.

Because I'm a well-behaved, team-playing dev, I TDD my code as I go, and having just added that exception-handling, I need to test itbeing about to add that exception-handling code, I need to write my tests first. So, anyway, whether I wrote the test before or after I wrote the code is neither here nor there. I came up with this test case:

<?php

namespace me\adamcameron\someApp\test\service;

use \namespace me\adamcameron\someApp\service\SomeService;

/** @coversDefaultClass \me\adamcameron\someApp\service\SomeService */
class SomeServiceTest extends PHPUnit_Framework_TestCase {

    public function setup() {
        $this->setMockedConnector();
        $this->testService = new SomeService($this->mockedConnector);
    }

    /**
        @covers ::getTheThingFromTheWebService
        @expectedException \me\adamcameron\someApp\exception\ServiceException
        @expectedExceptionMessage The Thing with ID 1 could not be retrieved
    */
    public function testGetTheThingsFromTheWebServiceWillThrowServiceExceptionWhenIdNotFound() {
        $testId = 1;

        $this->mockedConnector
            ->method('getThing')
            ->with($testId)
            ->will($this->throwException('\me\adamcameron\someApp\exception\NotFoundException'));
            
        $this->testService->getTheThingFromTheWebService($testId);
    }
    
    private function setMockedConnector(){
        $this->mockedConnector = $this->getMockBuilder('\me\adamcameron\connector\MyConnector')
            ->disableOriginalConstructor()
            ->setMethods(['getThing'])
            ->getMock();
    }

}

I've left a bunch of contextual and mocking code in there so you can better see what's going one. But the key parts of this test are the annotations regarding the exception.

(we're using an older version of PHPUnit, so we need to do this via annotations, not expectations, unfortunately. But anyway).

This went into code review, and I got some feedback (this is a paraphrase. Of, obviously, a completely fictitious conversation. Remember this is not really work code I'm discussing here):

  • Not too sure about the merits of testing the whole message string here. For the purposes of testing, we don't care about text, we just care about the $id being correct. Perhaps use @expectedExceptionMessageRegExp instead, with like "/.*1.*/" as the pattern.
  • not sure 1 is a great ID to mock, TBH

Well I can't fault the latter bit. If one is gonna be actually checking for values, then use values that are really unlikely to accidentally occur as side-effects of something else going on. Basically 0 and 1 are dumb test values. There's always gonna be a risk they'll end up bubbling up via an accident rather than on purpose. I usually use random prime numbers (often taken from this list of the first 1000 primes). They're just not likely to coincidentally show up somewhere else in one's results at random.

But the first bit is interesting. My first reaction was "well what's the important bit here? The number or the error message?" We wanna check the error message cos given that suggested regex pattern the exception could simply return 1, which is not help to anyone. And yet that'd pass the test. Surely the important thing is the guidance: "The Thing with ID [whatever] could not be retrieved".

But that's woolly thinking on my part. I'm not doing tests for the benefit of humans here. And no code is every gonna rely on the specifics of that message. TDD tests (and unit tests in general) are about testing logic, not "results". And they don't give a shit about the Human Interface. We've got QA for that.

There's no need to test PHP here. If we have this code:

throw new exception\ServiceException("The Thing with ID $id could not be retrieved");

We don't need to test:
  • that PHP knows what a string is
  • that PHP knows how to interpolate a variable
  • that one can pass a string to an Exception in PHP
  • etc
That's testing an external system (and showing no small amount of hubris whilst we do so!), and is outwith the remit of TDD or unit testing in general. If we can't trust PHP to know what a string is: we're in trouble.

What we do need to test is this:

public function getTheThingFromTheWebService($id){
    // bunch of stuff elided
    
    try {
        $result = $this->connector->getThing($id);
        
        // bunch of stuff elided
        
        return $finalResult;
    } catch (exception\NotFoundException $e) {
        // bunch of stuff elided
        
        throw new exception\ServiceException("The Thing with ID $id could not be retrieved"); 
    }
}


  • The ID comes in...
  • ... it's used...
  • ... and when the call that uses it fails...
  • ... we use it in the exceptional scenario.

The logic here is that it's the same ID that's passed to the function is the one reported in the exception message. Not what the exception message is: that's PHP's job.

So in reality here... we just need to test the ID is referenced in the exception. TBH, I'd not even usually bother doing that. It's just we're pushing to have more useful exception messages at the moment, so we're trying out "enforcing" this behaviour. I think it's a worthwhile exercise at least whilst the devs are getting used to it. Once it's part of our standard practice, I reckon we can stop worrying about what's in the error message.

But in the interim I'm gonna fix me test and see if I can pass this code review...

Righto.

--
Adam

Sunday, 25 September 2016

CFML: "It's alive!". Really now?

G'day:
Yesterday I was "helping" someone on the CFML Slack channel with some challenges they had with CFC serialisation. The conversation turned to ColdFusion's custom serialisers (see an earlier article: "ColdFusion 11: custom serialisers. More questions than answers"), and how pants I think the implementation & entire undertaking of that part of CF11 was. I'll just quote myself (full transcript of entire discussion here: Gist / adamcameron / slack.txt:

I did try to position the whole undertaking as "here be dragons"

On the face of it, it's a cool idea

But - as per most less-than-obvious things the Adobe CF Team tries to do - they completely cocked it up, having no real understanding of how ppl use CFML

[...]

What concerns me about Adobe's implementation is it seems to be very "not OO" which worries me when it's actually implemented by ppl who are supposedly Java devs.

[...]

I floated it "at the appropriate time" (nod to not speaking about the pre-release programme, but... well... f*** them), but the relevant parties just either didn't care or didn't get it or refused to get it.

It's the perpetual symptom that Adobe never consult the community before they blindly go and implement ill-conceived and poorly realised shite.

They ought to feel free to only consult with their major-client "IT stakeholders" for features like the API Manager and to a degree `<cfclient>` (hohoho), but when it comes to nuts 'n' bolts language implementation... they need to ask the ppl on the ground.

But based on that recent survey Adobe did... there won't be any more focus on the CFML language from them. They're only interested in buzzword-completion/box-ticking IT-Manager-pleasing platform features

In response to that someone observed they admired my sticking power with CFML (thanks for saying that), but my reaction was that Adobe finally "broke" me with CF2016 being such a waste of time for the CFML dev, as they did hardly anything in the language, instead of rolling something that's really nothing to do with ColdFusion: the API Manager into CF2016 and making it the marquee "feature".

At the same time Lucee 5 lost objective direction in that they indulged Micha's whim to re-write the thing to be OSGi capable (or whatever the correct term is), and mess around with the .lucee lang thing which ended up being both stillborn and evidence that LAS is out of their depth when they're not just copying what Adobe does. Harsh, sorry LAS, but that's my opinion.

I have given up on both the vendors when it comes to CFML, and personally I've moved from CFML to PHP now, but I'm still there in the CFML community helping people where I can. So - as far as the community goes - I'm suffering from "just when I thought I was out... they pull me back in", as it were. Apologies to Al Pacino there. But theatrics aside, that's my choice, so no worries.

Patrick came back with a reasonable riposte to my comments about being let down by the vendors:

Yeah, those sorts of public "divorces" (Railo-->Lucee) are never pretty, but it was really necessary. Kudos to the core of the Railo peeps who held it together. LAS is quite strong now, and I believe we're the future of CFML as a platform. I'm especially excited to see what the community can do with the extensible framework in place now. What say we all co-create a really vibrant ecosystem of products? With Adam as our cheerleader (I don't see it as cynical, mate!), I have no doubt we'll get it done.

Good on him, but I disagreed with one thing he said, and this lead to a bit of a diatribe from me:

You can't just say "What say we all co-create a really vibrant ecosystem of products?" and have it happen. The CFML community have demonstrated that they're - for the most part - takers rather than givers. Even with ppl asking for help from the community I see a sense of (misplaced) entitlement from them.

That said there's a minority of good worker bees, and a bunch of people who are good people but they're still just in CFML 9-5 (which is fine), but will not grow or strengthen the community.

Most of the stalwarts of the community are companies who have in the past invested a lot of effort into CFML (not the community necessarily, but for themselves), and it'd be a large $$$ cost for them to move. That's going to be why some of them continue to persist with CFML, and also encourage Lucee[:] as LAS is more likely to work with those small to mid-level companies to provide what they want out of CFML. Same as with Adobe and larger corporates.

The likes of Daemon and MSO.Net and Ortus and Pixl8 aren't going away when it comes to CFML due to how they've stacked their egg baskets, and I reckon they're more likely to stick with CFML than [it is that] LAS will [continue to] exist. Although one of them might consider "buying" Micha to perpetuate CFML for them if LAS founders. At which point Lucee will become kinda like OpenBD is for Alan Williamson's outfit

Sean was reading this and "emoting" with thumbs-ups in agreement and the like, then we got to chatting about OSS & CFML and that sort of thing. I'll spare you that.

But I stand by what I say above. I've been in the CFML community since 2001, and been active in the community all that time, and have been watching the ebb and flow of the CFML tide, be it ColdFusion or BlueDragon or Railo or Lucee. So I think my opinion is possibly an informed one. And I always offer it for the sake of informing, rather than posturing. People are quite welcome to disagree with me and to put a case forward as to why I'm mistaken. I like finding out I'm wrong, and I like debating (or arguing ;-) a topic with people. Provided they actually make a case.

Geoff Bowers (of afore-mentioned Daemon fame: he's the boss; and I think he's El Presidente of LAS) joined the conversation and suggested we should not be having such conversations, as they hurt the CFML community. And Geoff's position is that "The CFML Community" is a meaningful entity.

I disagree.

I disagree with a few points or inferences here:

  • Any conversation by experienced members of a given community will possibly have merit, even if they suggest that the best course of action for the community members is to prepare themselves for the time when "the community" doesn't really mean anything any more.
  • Even if the points being discussed have no merit, I disagree with the notion that the discussion should not be had because one member of the community doesn't like the sound of it, or it doesn't match their agenda.
  • I don't actually think "The CFML Community" is a thing in and of itself which automatically deserves preservation. This might seem odd, but I'm interested in the welfare of the members of the community, not some completely intangible label "The CFML Community". That's meaningless. I will help the individual people in the CFML community whenever I can (or, realistically, when I feel like it, but that's quite often ;-), and I think helpful advice to people who rely on CFML for their crust these days is: plan an exit strategy. It's convenient for Geoff to deny this (I'll get to that), but CFML as a language has had its day. It had a unique selling point a decade ago: it made making dynamic websites quite easy compared to where Perl or PHP or JSPs were at the time. It does not have that USP any more. CFML does a bunch of things really well, but not that much better than any number of more popular (and easy!) languages do these days. The thing is the other languages bring their own participatory communities with them, and with those communities come rich OSS efforts which ensure whatever one wants to do that might be off-piste for a language: there'll be a third-party module for that.


But I digress: no, I don't think "The CFML Community" is worth protecting. The community members are, and my best (-quality and -intentioned) advice is to start planning an exit strategy. You'd have to be an arsehole to place the preservation of "The CFML Community" ahead of the welfare of its members.

CFML has precisely one remaining relevant purpose: to keep CFML devs in work if they can't do anything else. CFML is not going places. It's staying where it is. But the ice floe where it's positioned itself is melting away, growing smaller and smaller, and nothing's gonna reverse this.

Geoff disagrees. That's cool, but other than "I disagree" and a few half-hearted ad hominems ("none taken", btw), he didn't actually refute a single thing I said. I'd be dead f***in' keen to hear what he has to say though, to demonstrate how CFML is still alive in any meaningful way though. And how it's a good prospect for uptake, and how CFMLers shouldn't be planning an exit strategy.

My cynicism suggests to me that Geoff is mostly concerned about "The CFML Community" because it's commercially prudent for him to do so: he's built a company and a livelihood on CFML. So it's beneficial to him for there to be "The CFML Community" out there, as they're potential revenue opportunities. That's completely fine and I see where he's coming from. But it seems disingenuous to me for him to be suggesting myself or others don't have the community's best interests at heart when we offer advice that is not convenient for his business model.

Another issue I'll touch on in this increasingly rambling diatribe is Geoff's assertion that my observations have an actual deleterious impact on the community. This is seemingly based on the notion that any negative comment is intrinsically deleterious, and for "names" such as myself to me making these negative comments is more deleterious still. I guess cos ppl might be inclined to listen to my advice. Heaven f***in' forbid.

He's probably right. But let's get things in perspective here. Things that are deleterious to "The CFML Community":

  1. ColdFusion being a paid-for product in 2016;
  2. Adobe's lack of interest or lack of ability to keep the language contemporary;
  3. every other bloody language being more contemporary and compelling;
  4. and free.
  5. The apathy of most CFML dev resulting in an under-represented community to engender momentum; 
  6. Adobe's lack of ability to market ColdFusion;
  7. Railo v Lucee schism;
  8. Lucee 5 taking an inordinate amount of time to see light of day, for what seems like developer-self-indulgence;
  9. LAS dithering about the place and seeming very incoherent when it comes to what they're doing;
  10. [daylight]
  11. [more frickin' daylight]
  12. Me saying stuff Geoff doesn't like.
One could quibble about some of those items there, but it's the scale of things that is significant. Almost all of CFML users don't know who the f*** I am. A decent subset of those that do can think for themselves and will take what I say with a grain of salt, or already be (dis-)agreeing with me. And the others could do worse than consider what I have to say, and weigh it up for themselves.

But no CFMLer who cannot form their own opinions will have any idea who I am, and will never see any of my written opinions.

I hasten to add that the list above is "things that are deleterious to the CFML community". I see Lucee as a net force for good in the community. But if we're assessing what is unhelpful in the community, then they're a more significant player in all this than I am. And even Lucee's influence one way or the other is completely inconsequential compared to Adobe's.

But go on then, Geoff and others who think CFML is alive and well and is something that should be suggested for new adopters. Tell me why it is. Tell us... "The CFML Community"... why that's the case. Don't just call me an uppity dick or a big meany or whatever the f*** personal attack is easier for you to make. Do something.

Why is CFML still relevant?
Why should new people adopt it?
Why should people stick with it instead of perhaps protecting their future by examining other options?
Why should people not at least suggest that might be something to do?

Go on then...

--
Adam

Friday, 23 September 2016

cflivedead.net

G'day:
This is disappointing. cflive.net was an online CFML runner which I used to use a lot to test small bits of code when I did not have a CFML server handy. Or couldn't be arsed starting one and saving code to a file etc.

I noticed that the Lucee server it ran was down the other day, and asked Russ about it, and he said it'd likely be down for a while. Today I notice the site is gone completely, replaced with:

[...] I am sorry to report, that Host Partners, who hosted CFDeveloper and CFLive have gone out of business, and as a result both sites will now be offline until further notice. [...]
Oh dear.

I know there's trycf.com but it's a bit quirky in how it runs its CFML and I always preferred cflive.net. I also know there's commandbox, which is cool but not quite as convenient as cflive.net.

Oh well. Thanks for providing the service whilst it lasted, Russ. I got a lot of benefit out of it, and indirectly so did a lot of other people who read my witterings on this blog.

Thanks for your selfless contribution to the CFML community.

In related news, I think there are some old articles on this blog that AJAXed calls off to cfmldeveloper and displayed the results. Obviously those AJAX calls will be broken now too. I dunno which articles they were, and there were only a few, so cannot be arsed finding them and repairing them. If you come across any which seem broken, lemme know and I'll decide if I feel like handling it in a different way.

Righto.

--
Adam

TIL: Cameron is bloody wrong again

G'day:
This is just a short one. I ballsed-up a coupla things in my previous article: "Another Friday puzzle; and the importance of tests that try to break code". It should have also read "and the importance of following the spec". Ahem.

So the bit of the question I read was this:

The maximum sum subarray problem consists in finding the maximum sum of a contiguous subsequence in an array or list of integers:

maxSequence([-2, 1, -3, 4, -1, 2, 1, -5, 4])
// should be 6: [4, -1, 2, 1]
And I admonished John and Isaiah for not dealing with sets with only negative numbers in them. My solution "worked" in that it returned the highest-total subset (basically a subset containing just the single highest negative number). They were returning 0, which I figured was an oversight / edge-case-failure.

However elsewhere, if I was to continue reading (or following links, or something), it did indeed say something about the minimum total being 0, even if the set contained only negative numbers. Oops.

Another contentious point was that I figured if the set was empty, then the result should be undefined or null. This is showing-up my lack of mathematical background, in that there's well-established rules for this, which Ryan pointed out to me: "Operations on the empty set". Basically it's taken as written that the sum of an empty set is zero. So it's official.

Now... whilst my solution was - as a result of these facts - wrong, I still think the exercise was a useful one, so I'll update that article to point to this, but otherwise leave it as is.

And that's it. Just thanks to John, Isaiah and Ryan for setting me straight there.

Righto.

--
Adam

Monday, 19 September 2016

Another Friday puzzle; and the importance of tests that try to break code

G'day:
There was another Friday Puzzle on the CFML Slack channel this week. The question was:

The maximum sum subarray problem consists in finding the maximum sum of a contiguous subsequence in an array or list of integers:

maxSequence([-2, 1, -3, 4, -1, 2, 1, -5, 4])
// should be 6: [4, -1, 2, 1]

Apparently the word "contiguous" is something that will flummox some people (judging by the discussion on the Slack channel). In this sense a contiguous sub-sequence is one that is made from adjacent array elements. So you can see they highlighted sub-sequence is a subset of adjacent array elements.

Eyeballing the example series here reveals the tricky bit one needs to watch for... negative values. Take a sub-sequence of 3,4. That has a sum of 7. Now if the next number is negative: 3,4,-1 (sum 6), the sum of this longer sub-sequence is less than that of the preceding shorter one, so the shorter subsequence is still the "winner". However if the next number (or numbers) sum to greater than the negative number, eg; 3,4,-1,2 (sum 8) then we've got the highest sum again. And of course there could be more negative numbers followed by positive numbers with a higher sum repeatedly.

Update:

I did not thoroughly read the requirement here, so got some of this wrong. See "TIL: Cameron is bloody wrong again".

A couple of the bods on the Slack channel quickly came up with solutions. Here they are, respectively: Isaiah's and John's. I'll only repeat one of them here because both used pretty much the same algorithm, just one used procedural code, and the other a more functional approach (in that they used higher-order functions). But they've both got the same logic error in them, because the approach is flawed. Let's have a look at John's one:

numeric function maxSequence(required array arr){
    var currentSum = 0;
    return arr.reduce(function(maxSum, number){
        currentSum = max(currentSum+number, 0);
        return max(currentSum, maxSum);
    }, 0);
}

WriteDump(maxSequence([-2, 1, -3, 4, -1, 2, 1, -5, 4]));

WriteDump(maxSequence([-2, 1, -3, 4, -1, 2, 1, -5, 4, 3]));

WriteDump(maxSequence([-2, -3, -1, -5]));

WriteDump(maxSequence([1, 4, 2, 1, 4, 3]));

This is nice and simple, and it took me a while to get what he's doing here. But we're keeping a running sum of the sequence outside the callback, and the callback itself returns the greater of the running sum or the whatever has previously been the maximum sum. So taking the first series there, we get the following iterations:

maxSumnumbercurrentSumnewMax
0-200
0111
1-301
1444
4-134
4255
5166
6-516
6456

Superficially this seems OK, except for one thing: it considers the minimum possible sum to be 0. Which is not correct. In a sequence with only negative numbers... the maximum sum will be the highest negative number: in [-1,-2,-3] the highest sum there is -1, but sums less than zero are ignored by this algorithm, so it incorrectly returns 0. Equally for an empty sequence, the answer is null or undefined, so this algorithm will fail there too, as it returns 0 for this too (as it defaults to a maximum sum of zero, whereas we don't know there'll be a sum when we start the process. So I guess that's two slightly different logic errors there.

I'll come back to John's solution in a bit, but first here's my solution. It's not as elegant as John's, and I'm not entirely happy with it, but it does work.

Here's the code (I've opted to do mine in ES2015 rather than CFML):

let sumLongestContiguousSubsequence = function (array) {
    let subSequences = array.map((_,i,a)=>a.slice(i));

    return subSequences.reduce(function(max, subsequence){
        let runningSum = subsequence[0];
        let maximumSubSequenceSum = subsequence.reduce(function(max, element){
            return (runningSum += element) > max ? runningSum : max;
        });
        return Math.max(max||maximumSubSequenceSum, maximumSubSequenceSum);
    }, null);
};

My conceit is that one cannot make assumptions about any earlier maximums, so I don't default the answer to anything.

Also I don't default the running sum to anything, I just take its starting point as the first element of the sequence I'm checking.

The general approach here is slightly more ham-fisted than John's approach.
  • I figure I need to scan each separate sub-sequence of the main sequence, starting each sub-sequence from each subsequent value in the main sequence. EG: if I have a sequence of [1,2,3,4], then subSequences will be [[1,2,3,4],[2,3,4] [3,4],[4]].
  • For each of those I do much the same thing as John: just run a cumulative sum, and remember whatever the highest value for that was.
  • So that'll bring me back to an array of maximums (in my [1,2,3,4] example, this'd be [10,9,7,4].
  • And as I reduce that lot, I simply return whichever is the higher of the previous ones and the current one.

I also wrote actual tests here. And this is where my approach differs from John's (or Isaiah's for that matter). When I test things I don't try to demonstrate to myself it works, I try to break the thing.

Here are my tests:

"use strict";

let assert = require("chai").assert;

let sumLongestContiguousSubsequence = require("./sumLongestContiguousSubsequence.js");

describe("Test of puzzle requirement", function(){
    it("returns the highest contiguous subseries sum for baseline requirement", function(){
        let sequence = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
        let expectation = 4 + -1 + 2 + 1; // 6
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
});

describe("Tests of other puzzle submission sequences", function(){
    it("returns the highest contiguous subseries sum for variation 1", function(){
        let sequence = [-2, 1, -3, 4, -1, 2, 1, -5, 4, 3];
        let expectation = 4 + -1 + 2 + 1 + -5 + 4 + 3; // 8
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum for variation 2", function(){
        let sequence = [-2, -1, -3, -5];
        let expectation = -1;
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum for variation 3", function(){
        let sequence = [1, 4, 2, 1, 4, 3];
        let expectation = 1 + 4 + 2 + 1 + 4 + 3; // 15
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
});

describe("Test edge cases", function(){
    it("returns the highest contiguous subseries sum with an empty array", function(){
        let sequence = [];
        let expectation = null;
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum with just zero", function(){
        let sequence = [0];
        let expectation = 0;
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum with just -1", function(){
        let sequence = [-1];
        let expectation = -1;
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum with just 1", function(){
        let sequence = [1];
        let expectation = 1;
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
});

describe("Better described tests", function(){
    it("returns the highest contiguous subseries sum when the sequence has negative values", function(){
        let sequence = [1,2,3,-11];
        let expectation = 1 + 2 + 3; // 6
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum when the sequence has negative values followed by a greater positive value", function(){
        let sequence = [1,2,3,-4,5];
        let expectation = 1 + 2 + 3 + -4 + 5; // 7
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum when the sequence has negative values followed by a subseries positive values that are net greater than the negative one", function(){
        let sequence = [2,4,6,-8,3,7];
        let expectation = 2 + 4 + 6 + -8 + 3 + 7; // 14
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
    it("returns the highest contiguous subseries sum when the sequence has repeated negative values followed by a subseries positive values that are net greater than the negative one", function(){
        let sequence = [12,14,16,-8,3,7,-12,5,9];
        let expectation = 12 + 14 + 16 + -8 + 3 +7 + -12 + 5 + 9; // 46
        let result = sumLongestContiguousSubsequence(sequence);
        assert.equal(expectation, result);
    });
});

These are fairly deliberate in what they test, but don't try to do anything clever (so there's a lot of repeated code). I make a point of testing edge cases like empty sequences, and sequences with only one element, and all negative values and all positive values etc. My philosophy with testing is that I'm not trying to demonstrate the code works, I'm trying to demonstrate it doesn't break. This amounts to the same thing, but it starts from a slightly different mindset. Testing is one situation where "glass half empty" rather than "glass half full" is the better way to look at things.

So this version of the solution works, but I'm not happy about it. For one thing, I don't think one can look at it and go "right, it's clear what's going on there". I looked for refactoring opportunities, but am not seeing any. Maybe subSequences could have a better name? I looked at extracting the callbacks into named function expressions, but that didn't look any clearer to me either.

let sumLongestContiguousSubsequence = function (array) {
    let runningSum;
    
    let getCurrentMaxSum = function(max, element){
        return (runningSum += element) > max ? runningSum : max;
    };
    
    let findSumOfLongestSub = function(max, subsequence){
        runningSum = subsequence[0];
        let maximumSubSequenceSum = subsequence.reduce(getCurrentMaxSum);
        return Math.max(max||maximumSubSequenceSum, maximumSubSequenceSum);
    };

    let subSequencesSlicedAtEachIndex = array.map((_,i,a)=>a.slice(i));

    return subSequencesSlicedAtEachIndex.reduce(findSumOfLongestSub, null);
};

What do you think: is that clearer? I guess it's a bit better, innit? It's straying away from "elegance in simplicity" though.

Ha, just for a laugh I took the code clarity in the opposite direction. How about this mess:

let f=a=>a.map((_,i,a)=>a.slice(i)).reduce((m1,s)=>{
    let t=s[0],m2=s.reduce((m,v)=>(t+=v)>m?t:m)
    return Math.max(m1||m2,m2)
},null)

It's the same logic as my original version.

The other issue that Sean and Ryan are likely to pull me up on is that one of my reductions relies on side-effects spilling out into the intermediary calling code:

let sumLongestContiguousSubsequence = function (array) {
    let subSequences = array.map((_,i,a)=>a.slice(i));

    return subSequences.reduce(function(max, subsequence){
        let runningSum = subsequence[0];
        let maximumSubSequenceSum = subsequence.reduce(function(max, element){
            return (runningSum += element) > max ? runningSum : max;
        });
        return Math.max(max||maximumSubSequenceSum, maximumSubSequenceSum);
    }, null);
};


This didn't actually bother me until I did the PHP version of this, where the side-effects are more glaring:

$sumLongestContiguousSubsequence = function ($array) {
    $subSequences = array_map(function($i) use ($array) {
        return array_slice($array, $i);
    }, array_keys($array));

    return array_reduce($subSequences, function($max, $subSequence){
        $runningSum = 0;
        $maximumSubSequenceSum = array_reduce($subSequence, function($max, $element) use (&$runningSum){
            return ($runningSum += $element) > $max ? $runningSum : $max;
        }, $subSequence[0]);
        return max($max?:$maximumSubSequenceSum, $maximumSubSequenceSum);
    }, null);
};

PHP sux a bit because it doesn't do closure implicitly, one needs to tell it what to enclose, which is clumsy IMO. But it's also a bit of an orange flag. This orange flag becomes a red flag when I need to actually use a reference here, cos I'm changing the original value, not simply using it.

That guilt-tripped me into revising my JS version so as to not need the side effects:

let sumLongestContiguousSubsequence = function (array) {
    let subSequences = array.map((_,i,a)=>a.slice(i));

    return subSequences.reduce(function(max, subsequence){
        let maximumSubSequence = subsequence.reduce(function(working, element){
            working.runningSum += element;
            working.max = working.max || working.runningSum;
            working.max = working.runningSum > working.max ? working.runningSum : working.max
            return {max:working.max, runningSum:working.runningSum};
        }, {runningSum:0});
        return Math.max(max||maximumSubSequence.max, maximumSubSequence.max);
    }, null);
};


Now I'm carrying both the runningSum and the max value through the first argument in the reduction, by using an object rather than just the simple value. It's a small change but gets rid of the smell. To be completely honest though... I prefer my original version. I realise it's frowned-upon to bleed side-effects (which I then leverage), but this refactoring seems like an exercise in pedantic/dogmatic busy-work, and makes the code slightly less clear in the process. I dunno.

Oh, for completeness I did a CFML conversion too:

sumLongestContiguousSubsequence = function (array) {
    var subSequences = array.map((_,i,a) => a.slice(i));
    return subSequences.reduce(function(maxSum, subSequence){
        var runningSum = 0;
        var maximumSubSequenceSum = subSequence.reduce(
            (maxSum, element) => (runningSum += element) > maxSum ? runningSum : maxSum,
            subSequence[1]
        );
        return max(maxSum ?: maximumSubSequenceSum, maximumSubSequenceSum);
    }, null);
};

Note that this solution only works on Lucee, not ColdFusion, for three reasons:
  • ColdFusion does not have arrow functions;
  • ColdFusion has a bug in its parser which breaks on this expression (see 4190163);
  • Lucee has a null keyword whereas ColdFusion does not.

The Lucee version is OK though. Same as the JS version for the most part: just 1-based arrays, not 0-based; and Lucee has the ?: operator whereas JS uses ||.

That's about all I can think to say about this. I might see if I can mess with John's approach to make it work for those two edge-cases it fails on. I much prefer his way compared to my way... but only provided it can be made to work! I'd also be keen to see treatments of this puzzle in other languages, or better/different solutions for JS, PHP or CFML.

Righto.

--
Adam

Thursday, 8 September 2016

My Dad

This article is very off topic for this blog.

My dad, Don Cameron, was born way back in the 1930s, in Dunedin in the South Island of New Zealand. Dunedin was a small city at the far end of a small country distant from the rest of the world both geographically and temporally. South Islanders have the reputation of being no-nonsense sort of people, who are honest and - if they say anything at all - say what they mean. Dad was a bit chattier than yer average South Islander perhaps, but he was definitely a man of that time and place. He was the youngest of a batch of seven kids.

Later the Cameron family moved to Auckland, and Dad did a bit of his schooling in Auckland at St Peter's, and a bit of it back down in Oamaru in the South Island, at St Kevin's - a catholic boarding school. The family were fairly staunch (some might say austere) Scottish Catholics. I'm not the family historian so I'm gonna be light on dates here... I base my knowledge of Dad's past on his stories and anecdotes. My sister - who is the family historian - will be shaking her head in disappointment here.

Dad played rugby and cricket as all NZ lads do, and - from what I understand - was a pretty good cricketer in his time.

Returning to Auckland and at some stage after finishing school and wondering what to do next, Dad decided a job was in order. So he walked downtown and into the NZ Herald building (it used to be on Queen Street). The Herald was the main morning newspaper in Auckland and the top half of the North Island. Dad ended up heading in there for a job cos - I think - some friend or cousin or someone his mum or dad or aunty or someone knew put in a good word or something, as was the way of things in 1950. And shortly after that, he exited the building with a job. 48 years later he retired from that job.

I dunno what he did when he started, I think running copy around the place and proofreading and stuff, but Dad's career-proper got underway when he joined the sports department, basically in an intelligent sporting person's dream job: being paid to watch sport. Particularly the sports he loved the most, rugby and cricket. Although he also covered sailing and golf too. And probably anything else that needed reporting on at the time.

I am in awe of Dad that that's what he did for a living.

Along the way the job took him to the West Indies for cricket, all around Europe with the All Blacks for rugby,  and the UK for cricket, to the Indian subcontinent and Sri Lanka for cricket, and back and forth to Aussie for both sports almost like it was his second home. He even found himself in South Africa during the apartheid era for a rebel rugby tour.

Rolling back a bit, in the mid 50s Dad met Mum and they dated and got married in 1958, and me big brother came along in 1959 and me big sister in 1961.  There was a bit of a gap and they adopted me in 1970. I always liked holding over my bro and sis that M&D were just landed with them; they actively chose me. What a little shit ;-)

They also bought a house in 1958, and I'm sitting there right now as I type this. 2m away from the bed that was mine from 1970-1989 when I moved out. I'd be sitting on the same bed if it was still there, but the room's been moved around a bit. Other than that not much has changed in this house. Kids and dogs and cats (and goldfish and canaries and axolotls and so on and so forth) came and went, but Mum and Dad remained. Oh and there were a coupla grandchildren showing up along the way too.

Mum and Dad were a great continuity in all our lives. Things just didn't change. It's good that some things don't change. I liked being able to fly halfway around the world back to NZ, walk into their house, past them ("yeah g'day Mum; g'day Dad [keeps walking towards the kitchen]"), to the fridge and locate the same old block of cheese that was always in there, and make meself a snack. OK, it was a different block of cheese each time, but it amounts to the same thing. You know what I mean. Mmm... cheese. But I digress.

Things, unfortunately, don't stay the same forever. in 2016, Mum and Dad were both in their early 80s. Things get tougher as one gets old, and unfortuately Mum became ill and was too ill for Dad to look after by himself. So she's moved away from the family home and into a nearby care home. We kids started to wonder how well Dad would get on by himself, whilst also not being that comfortable with the idea of finally surrendering the house that had been all our family home for the entire time we existed as a family (even M&D at least owned this house before they moved into it when they got married).

To use a cliche that would annoy my father, fate intervened. He had a fall, and being old: that isn't as trivial as it sounds. He ended up in hospital, and they did some tests and poked and prodded him and tried to make him better, but as well as the fall they found a few other things wrong with him. And he wasn't ever gonna be leaving the hospital. Well... he left the hospital after a month or so and moved into the same care home Mum was at.

Things were going down hill, so last week I decided I better head back from the UK to NZ to... well... pretty much say my good-byes to me old man. I did not want the next time I was heading back to NZ to be his funeral, which sounded like was going to be taking place before the end of the year.

I got back to NZ last week, and saw Dad on Friday-last. I'm sad to say it was clear he was on his way out, and have been kicking myself that I did not get back sooner. But I got to see him, and more importantly he got to see me, and we spent some time together. We had a good Fathers' Day on Sunday: we got Mum up from her ward to sit with Dad for a while. She told him it might be time to go to sleep.

After that they had to up his meds so he was not aware of his surroundings any more. But we were there, my brother, myself; and mostly my sister who stayed by his side relentlessly each day.

But... and you could probably predict where this was going... at about 1pm this Wednesday gone, my father died. I was sitting here - right here - playing f***ing Skyrim at the time. WhyTF was I not there? Well... that's something that can't be undone. And in the bigger scheme of things: doesn't matter I guess.

I read a quip on Twitter the other day reminding us tech-industry people that next time our parents seem frustrating because we need to show them how to do the most mundane things on their computers, that they had to take the time to teach us how to even use a spoon.

My dad taught me how to use a spoon. He wiped my bum. He clothed me and he housed me. He saw to it I went to a good school. He praised me on the odd occasion I did something good; he softly admonished me when I got caught out not being so good. Occasionally I was chuffed cos I could tell he was proud of me. He tried (and for the most part failed) to teach me to bat and bowl, and to pass a rugby ball. And to play pool. My dad taught me how to play pool. How cool is that? He taught me to drive. He didn't get too f***ed off when I crashed his car. Again. And that other time. He bought me beer, and let me buy him beer.

Through all this I never really realised exactly how much my dad loved me until my own son came along, five-or-so years ago. One thing I think I did right is that I got on the phone to him straight away and told him I finally knew how much he loved me. As a father loves his children.

I love my dad so much. And I miss him so much. And I hadn't finished letting him buy me beers yet, and telling me yet another new story about his adventures in sports reporting, and his life in general as a bit of a scallywag.

This is the only photo of Dad, my boy Zachary, and meself:



Donald ("DJ") Cameron. 1933-02-20 - 2016-09-07. I hadn't finished buying you beer, Dad.

--
Donald Cameron jr.



More professional obits: