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. 46 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. Us 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:

Saturday, 3 September 2016

JS: next Friday puzzle: displaying bytes in "nearest unit"

G'day:
Here's my answer for the next Friday puzzle that was posted on the CFML Slack channel. I skipped last week's one as... well... I couldn't be arsed, but this was an easy one and I could sort it out whilst cursing my jet lag (I've just done London -> Auckland this week).

The details of the puzzle are in this gist, but basically it's to take a number of bytes and "round" it to the appropriate closest unit of bytes (eg: kB, MB, GB etc), up to PB. There's more detail than that, but that's the bit I'm paying attention do. Doing CFML is a waste of time, so this week I've decided to do it in JavaScript instead, and re-polish my piss-poor ES2015 / Node.js / Mocha skills. Although not too much.

First I tried a solution which treated the number as a string and just reduced it down to the most significant figures and slapped a unit onto the end of it. Whilst this did what I set out to do, it only dealt with decimal groupings, not 1024-based ones. Here's the code anyhow. It's not polished up cos I abandoned it half-way through:

f = function(x) {
    var units = ["B", "kB", "MB", "GB", "TB", "PB"];
    var ordersOfMagnitude = 3;

    var numberAsString = x.toString();
    var lengthOfNumber = numberAsString.length; 

    var numberOfDigits = lengthOfNumber % ordersOfMagnitude;
    var unitIndex = Math.floor(lengthOfNumber / ordersOfMagnitude);

    if (numberOfDigits == 0) {
        numberOfDigits = 3;
        unitIndex--;
    }
    if (unitIndex+1 > units.length){
        unitIndex = units.length - 1;
        numberOfDigits = lengthOfNumber - (unitIndex * ordersOfMagnitude);
    }
    
    var digits = numberAsString.substring(0, numberOfDigits);
    var unitToUse = units[unitIndex];

    var result = digits + unitToUse;
    
    return result;
}

This was more just a spike to get me thinking.

Then I tried another version where I did a reduction on the units array, but that was crap as it was quite side-effect-y, and I don't really think it was a good use of a reduce operation. I did not keep the code for that one, so I can't show you.

Next I decided to stop messing around and come up with an answer that actually worked and wasn't daft, so I knocked this one together:

"use strict";
 
let numberToMemoryUnits = function(bytes) {
    let units = ["kB", "MB", "GB", "TB", "PB"];
    let binaryDivisor = 1024;
    let numberOfBytesAsUnit = bytes;
    let unit = "B";
    while (numberOfBytesAsUnit >= binaryDivisor && units.length){
        numberOfBytesAsUnit /= binaryDivisor;
        unit = units.shift();
    }
    let roundedValue = Math.floor(numberOfBytesAsUnit);

    return `${roundedValue}${unit}`;
}

module.exports = numberToMemoryUnits;

That's OK-ish I guess. The only thing I don't like is how I have that first assignment of the units separate to the loop. It seems like dodgy code doubling-up to me, and I should get rid of that somehow, but can't be bothered thinking it through (I'm slightly hungover, which doesn't help).

Update:

I've tweaked this slightly since I first posted it:
  • Improved the argument name from a rather lazy-arsed x to the more descriptive bytes.
  • Likewise improved the name of the variable which was digits to be numberOfBytesAsUnit.
  • Used the intermediary variable roundedValue,
  • and used an interpolated string instead of string concatenation with the return value.
  • improved the way I initialised the units object in the tests, from being individual assignment statements to being a reduction of the units array.
This was off the back of a discussion about my implementation that I had with Brendan on the Slack channel. He asked me why I had the separate variable digits (now numberOfBytesAsUnit) instead of just using the argument x (bytes). This was because whilst the value passed to the function is indeed a number of bytes, once I start using it - specifically in that loop - it's no longer a value in bytes, so I use a new - more descriptively accurate - variable name instead. We also discussed my separate handling of bytes and the other units coming from the array, and I'm still not 100% happy with it, but in using the better variable names around it I mind it less than I did before.

Any other code review input is welcomed, btw.

I was a bit naughty as I tested it by hand whilst developing it, but then felt guilty and formalised a bunch of tests after the fact. I guess I still did TDD whilst throwing this together, but not as deliberately as perhaps I shoulda. Anyhow, here are the tests too:

"use strict";

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

let numberToMemoryUnits = require("../src/numberToMemoryUnits.js");

let binaryFactor = 1024;

let units = ["kB", "MB", "GB", "TB", "PB"].reduce(function(units, unit, index){
    units[unit] = Math.pow(binaryFactor, index + 1);
    return units;
}, {});

describe("Tests for each unit", function(){
    it("should work for bytes", function(){
        let result = numberToMemoryUnits(123);
        let expectation = "123B";
        assert.equal(expectation, result);
    });
    it("should work for kB", function(){
        let result = numberToMemoryUnits(2345);
        let expectation = "2kB";
        assert.equal(expectation, result);
    });
    it("should work for MB", function(){
        let result = numberToMemoryUnits(3456789);
        let expectation = "3MB";
        assert.equal(expectation, result);
    });
    it("should work for GB", function(){
        let result = numberToMemoryUnits(4567890123);
        let expectation = "4GB";
        assert.equal(expectation, result);
    });
    it("should work for TB", function(){
        let result = numberToMemoryUnits(5678901234567);
        let expectation = "5TB";
        assert.equal(expectation, result);
    });
    it("should work for PB", function(){
        let result = numberToMemoryUnits(6789012345678901);
        let expectation = "6PB";
        assert.equal(expectation, result);
    });
    
});

describe("Test exact units", function(){
    it("should work for 1kB", function(){
        let result = numberToMemoryUnits(units.kB);
        let expectation = "1kB";
        assert.equal(expectation, result);
    });
    it("should work for 1MB", function(){
        let result = numberToMemoryUnits(units.MB);
        let expectation = "1MB";
        assert.equal(expectation, result);
    });
    it("should work for 1GB", function(){
        let result = numberToMemoryUnits(units.GB);
        let expectation = "1GB";
        assert.equal(expectation, result);
    });
    it("should work for 1TB", function(){
        let result = numberToMemoryUnits(units.TB);
        let expectation = "1TB";
        assert.equal(expectation, result);
    });
    it("should work for 1PB", function(){
        let result = numberToMemoryUnits(units.PB);
        let expectation = "1PB";
        assert.equal(expectation, result);
    });
});
describe("Test off by one", function(){
    it("should work for <1kB", function(){
        let result = numberToMemoryUnits(units.kB-1);
        let expectation = "1023B";
        assert.equal(expectation, result);
    });
    it("should work for >1kB", function(){
        let result = numberToMemoryUnits(units.kB+1);
        let expectation = "1kB";
        assert.equal(expectation, result);
    });
    it("should work for <1MB", function(){
        let result = numberToMemoryUnits(units.MB-1);
        let expectation = "1023kB";
        assert.equal(expectation, result);
    });
    it("should work for >1MB", function(){
        let result = numberToMemoryUnits(units.MB+1);
        let expectation = "1MB";
        assert.equal(expectation, result);
    });
    it("should work for <1GB", function(){
        let result = numberToMemoryUnits(units.GB-1);
        let expectation = "1023MB";
        assert.equal(expectation, result);
    });
    it("should work for >1GB", function(){
        let result = numberToMemoryUnits(units.GB+1);
        let expectation = "1GB";
        assert.equal(expectation, result);
    });
    it("should work for <1TB", function(){
        let result = numberToMemoryUnits(units.TB-1);
        let expectation = "1023GB";
        assert.equal(expectation, result);
    });
    it("should work for >1TB", function(){
        let result = numberToMemoryUnits(units.TB+1);
        let expectation = "1TB";
        assert.equal(expectation, result);
    });
    it("should work for <1PB", function(){
        let result = numberToMemoryUnits(units.PB-1);
        let expectation = "1023TB";
        assert.equal(expectation, result);
    });
    it("should work for >1PB", function(){
        let result = numberToMemoryUnits(units.PB+1);
        let expectation = "1PB";
        assert.equal(expectation, result);
    });
});

describe("Test boundaries", function(){
    it("should work for 0bytes", function(){
        let result = numberToMemoryUnits(0);
        let expectation = "0B";
        assert.equal(expectation, result);
    });
    it("should work for 1024PB", function(){
        let result = numberToMemoryUnits(units.PB*units.kB);
        let expectation = "1024PB";
        assert.equal(expectation, result);
    });
    it("should work for 1048576PB", function(){
        let result = numberToMemoryUnits(units.PB*units.MB);
        let expectation = "1048576PB";
        assert.equal(expectation, result);
    });
});


They're a bit long-winded in total, but the individual tests are simple enough. The key here is I test either side of each each unit, eg: 1023 is presented in bytes, 1024 is 1kB and so is 1025 etc. Also a test of zero for good measure, as well that it handles numbers beyond PB, and just uses PB thereafter (eg: 1025PB displays as such).

And they all pass:
C:\src\js\puzzle\20160903>mocha test\numberToMemoryUnitsTest.js


  Tests for each unit
    should work for bytes
    should work for kB
    should work for MB
    should work for GB
    should work for TB
    should work for PB

  Test exact units
    should work for 1kB
    should work for 1MB
    should work for 1GB
    should work for 1TB
    should work for 1PB

  Test off by one
    should work for <1kB
    should work for >1kB
    should work for <1MB
    should work for >1MB
    should work for <1GB
    should work for >1GB
    should work for <1TB
    should work for >1TB
    should work for <1PB
    should work for >1PB

  Test boundaries
    should work for 0bytes
    should work for 1024PB
    should work for 1048576PB


  24 passing (31ms)


C:\src\js\puzzle\20160903>


That's it. Nothing special today, but still required some thought, and also reminding myself how Mocha works.

Give it a go. Do it in some other language than CFML!

Righto.

--
Adam

Sunday, 28 August 2016

Blimey, a CFML-centric article

G'day:
But it's nothing that interesting. I submitted my answer ("Friday puzzle: my PHP answer") for the Friday Code Puzzle yesterday, deciding to use PHP, given it's what I do for a crust these days, and I could best visualise the solution in PHP.

But the puzzle was from a CFML chat group, so I figured I might blow the dust of my CFML skills and see how closely a CFML answer would approximate my PHP one. The answer is "quite closely!". Here's a CFML transliteration of the PHP answer:

component {

    parents = {};

    function init() {
        parents[0]["children"] = [];

        return this;
    }

    static function loadFromCsv(filePath) {
        var dataFile = fileOpen(filePath, "read");

        var tree = new Tree();
        while(!fileIsEof(dataFile)){
            var line = fileReadLine(dataFile);
            tree.addNode(
                nodeText = line.listLast(),
                id = line.listFirst(),
                parent = line.listGetAt(2, ",", true)
            );
        }

        return tree;
    }

    private function addNode(nodeText, id, parent) {
        parent = parent == "" ? 0 : parent;

        parents[id].nodeText = nodeText;
        parents[parent].children = parents[parent].children ?: [];
        parents[parent].children.append(parents[id]);
    }

    function serializeJson() {
        return serializeJson(parents[0]["children"]);
    }
}

Note that this is using Lucee 5's CFML vernacular, and this will not run on ColdFusion for a coupla reasons:

  • ColdFusion still doesn't support static methods;
  • It has a new bug with the ?: operator which breaks some of my shortcuts there.

I did not write the full test suite, but I wrote a small test rig and ran all the test variations manually. Here's the trickiest one:

dataFile = expandPath("../../test/testfiles/notOrdered/data.csv");

tree = Tree::loadFromCsv(dataFile);
treeAsJson = tree.serializeJson();

jsonAsArray = deserializeJson(treeAsJson);
writeDump(jsonAsArray);

And it yields the goods:




For completeness, here's the variation I had to do to make this work on ColdFusion:

static function loadFromCsv(filePath) {
    var dataFile = fileOpen(filePath, "read");

    var tree = new Tree();
    while(!fileIsEof(dataFile)){
        var line = fileReadLine(dataFile);
        tree.addNode(
            nodeText = line.listLast(),
            id = line.listFirst(),
            parent = line.listGetAt(2, ",", true)
        );
    }
    return this;
}

private function addNode(nodeText, id, parent) {
    parent = parent == "" ? 0 : parent;

    parents[id] = parents.keyExists(id) ? parents[id] : {};
    parents[id].nodeText = nodeText;
    
    parents[parent] = parents.keyExists(parent) ? parents[parent] : {};
    parents[parent].children = parents[parent].keyExists("children") ? parents[parent].children : [];
    parents[parent].children.append(parents[id]);
}

  • ColdFusion does not yet support static methods, so I cannot use a static factory method to create the tree, I need to instantiate an empty tree and then get it to load itself. Not ideal, but not the end of the world either.
  • Because of various bugs in ColdFusion's implementation of implicitly creating "deep" data structures, and also with the ?: operator, I had to be more explicit with stepping through creation of intermediary structs, and also use keyExists instead of ?:

This isn't bad, and I don't fault ColdFusion for its lack of static yet, but I'm annoyed by finding yet more bugs in ColdFusion when all I want is to run some code.

Back to the Lucee version: I think it demonstrates CFML syntax is a bit tidier than PHP's, as it's not quite so in love with the punctuation, and punctuation is just noise to achieving good Clean Code. But in this example it's much of a muchness, innit?

Anyway, I have little else to add other than that. The approach is identical to the PHP one I did: I just wanted to compare the two languages.

Job done.

Righto.

--
Adam