Showing posts with label Code Examples. Show all posts
Showing posts with label Code Examples. Show all posts

Thursday 1 July 2021

One last one! CFML higher-order functions compared to tag-based code: reduceRight

G'day:

I forgot one!

I've already discussed map, reduce, filter, sort, some, every and each, operations; but recently reduceRight was added to CFML (well: at least in ColdFusion it was; it's not in Lucee yet) as well.

I have to start my day job in 16min, so this will be quick.

reduceRight is the same as reduce, except it starts from the end of the collection, not the beginning:

colours = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Poropango","Papura"]

coloursAsList = colours.reduce((all="", colour) => all.listAppend(colour))
coloursAsReversedList = colours.reduceRight((all="", colour) => all.listAppend(colour))

writeOutput("coloursAsList: #coloursAsList#<br>coloursAsReversedList: #coloursAsReversedList#<br>")
coloursAsList: Whero,Karaka,Kowhai,Kakariki,Kikorangi,Poropango,Papura
coloursAsReversedList: Papura,Poropango,Kikorangi,Kakariki,Kowhai,Karaka,Whero

Yes yes Mingo; one would not use reduce to convert an array of strings to a list. That is beside the point. But thanks for letting me know Lucee (but not ColdFusion) has an Array.reverse method, which would be a better way to reverse the list order here: colours.reverse().toList().

And the tags version, just a reversed counting loop does the trick here:

<cfset coloursAsReversedList = "">
<cfloop index="i" from="#arrayLen(colours)#" to="1" step="-1">
    <cfset coloursAsReversedList = listAppend(coloursAsReversedList, colours[i])>
</cfloop>

That's it. four minutes to get to work. Fortunately that's just a matter of switching desktops…

Righto.

--
Adam

CFML higher-order functions compared to tag-based code: some, every and each functions

G'day:

I'm gonna try to round out this short series today: there's not much to say about the some, every and each methods in the context of comparing their functionality to old-school tag-based code. As a reminder, I've already covered map, reduce, filter and sort operations.

some

some iterates over the collection, calling a callback on each element, and will exit as soon as the callback returns true for an element. An example might be checking if at least some class members passed (or failed) their test:

examResults = [
    {person="Alex", mark=75},
    {person="Billie", mark=52},
    {person="Charlie", mark=41},
    {person="Daryl", mark=29},
    {person="Evan", mark=53}
]

somePassed = examResults.some((result) => result.mark >= 50)

writeOutput("Some of the class passed the test? #somePassed#<br><hr>")


someFailed = examResults.some((result) => {
    writeOutput("Called for #result.person#, #result.mark#<br>")
    return result.mark < 50
})

In the second example there I show a difference between this iteration function and the others we've encountered so far. All the others always iterate through the entire collection, however some and every do not. They exit as soon as they can answer the question. So as soon as some gets a true it exits; as soon as every gets a false it exits. The output of this is:

Some of the class passed the test? true

Called for Alex, 75
Called for Billie, 52
Called for Charlie, 41

In this case it only got as far as the first false result from the callback (because, sadly, Charlie did not make the cut)

The tag-based version of this would be:

<cfset somePassed = false>
<cfloop array="#examResults#" item="result">
    <cfif result.mark GTE 50>
        <cfset somePassed = true>
        <cfbreak>
    </cfif>
</cfloop>
<cfoutput>Some of the class passed the test? #somePassed#<br><hr></cfoutput>

<cfset someFailed = false>
<cfloop array="#examResults#" item="result">
    <cfoutput>Called for #result.person#, #result.mark#<br></cfoutput>
    <cfif result.mark LT 50>
        <cfset someFailed = true>
        <cfbreak>
    </cfif>
</cfloop>

Again with the boilerplate code (ref from previous articles).

BTW, don't get carried away with these higher-order functions if there's another built-in function to do the job. Recently I checked if something was in an array by doing this:

colours = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Poropango","Papura"]

containsGreen = colours.some((colour) => colour == "Kakariki")
writeOutput("It contains green: #containsGreen#<br>")

My boss gently pointed out I could just do this:

containsGreen = !!colours.find("Kakariki")

Use the simpler option where possible ;-)

every

every is the opposite of some: it exits as soon as the callback returns false. Our example here would be to check if everyone passed the exam:

everyonePassed = examResults.every((result) => {
    writeOutput("Called for #result.person#, #result.mark#<br>")
    return result.mark >= 50
})
writeOutput("Everyone passed the test? #everyonePassed#<br><hr>")
Called for Alex, 75
Called for Billie, 52
Called for Charlie, 41
Everyone passed the test? false

The tag-based equivalent is the usual "mostly boilerplate" thing:

<cfset everyonePassed = true>
<cfloop array="#examResults#" item="result">
    <cfoutput>Called for #result.person#, #result.mark#<br></cfoutput>
    <cfset personPassed =  result.mark GTE 50>
    <cfif NOT personPassed>
        <cfset everyonePassed = false>
        <cfbreak>
    </cfif>
</cfloop>
<cfoutput>Everyone passed the test? #everyonePassed#<br><hr></cfoutput>

each

Sometimes it's not a data transformation that one needs when iterating over a collection. If none of the other options do the trick, there's the generic each method:

examResults.each((result) => {
    writeOutput("Name: #result.person#, mark: #result.mark#<br>")
})

As a general rule never start solving an iteration task with each. Consider if one of the other more situation-specific methods are a better fit. It's seldom that each is the right answer.

And the tag equivalent is pretty much the same, because - really - all the tag version does is "each"; it's down to the inner code block to distinguish between the various iteration possibilities:

<cfloop array="#examResults#" item="result">
    <cfoutput>Name: #result.person#, mark: #result.mark#<br></cfoutput>
</cfloop>

OK that's it. Tag-based CFML versions of the more situation-descriptive and less boilerplate iteration higher-order functions. If you need anything else about them explained, let me know.

Righto.

--
Adam

Wednesday 30 June 2021

CFML higher-order functions compared to tag-based code: sort function

G'day:

OK so you've probably got the gist of things with these articles, with my previous treatments of comparing "modern" to "old school" with map, reduce, filter operations. On to sorting now.

I think this is going to involve some awful code.

I don't think I need to explain why we might need to sort a collection, or what "sorting" is. It's really easy using higher-order functions. The need to write the sorting algorithm has been removed, and only a function to compare to elements needs to be provided:

months = [
    {id=1, miSequence=8, mi="Kohi-tātea", anglicised="Hānuere", en="January"}, 
    {id=2, miSequence=9, mi="Hui-tanguru", anglicised="Pēpuere", en="February"}, 
    {id=3, miSequence=10, mi="Poutū-te-rangi", anglicised="Maehe", en="March"}, 
    {id=4, miSequence=11, mi="Paenga-whāwhā", anglicised="Āperira", en="April"}, 
    {id=5, miSequence=12, mi="Haratua", anglicised="Mei", en="May"}, 
    {id=6, miSequence=1, mi="Pipiri", anglicised="Hune", en="June"}, 
    {id=7, miSequence=2, mi="Hōngongoi", anglicised="Hūrae", en="July"}, 
    {id=8, miSequence=3, mi="Here-turi-kōkā", anglicised="Akuhata", en="August"}, 
    {id=9, miSequence=4, mi="Mahuru", anglicised="Hepetema", en="September"}, 
    {id=10, miSequence=5, mi="Whiringa-ā-nuku", anglicised="Oketopa", en="October"}, 
    {id=11, miSequence=6, mi="Whiringa-ā-rangi", anglicised="Noema", en="November"}, 
    {id=12, miSequence=7, mi="Hakihea", anglicised="Tihema", en="December"}
]
monthsInMaoriOrder = duplicate(months).sort((e1, e2) => e1.miSequence - e2.miSequence)

writeDump(monthsInMaoriOrder)

Here I have a list of the months of the year, ordered according to the Gregorian calendar. The Maori calendar has the same ordering, but the year starts around when the Gregorian calendar considers June. So the exercise here is to re-order the array to respect that ordering. The code for the sorting is just the comparator function.

One thing to note here is that despite appearances given we're assigning the return value of the sorting operation to a new variable, the original array is modified when you call sort on it. I think this is less than ideal, but it's the way it works on both ColdFusion and Lucee. If you want you're original array left alone, then duplicate it first like I have here.

If we're going old school procedural: it's a bit of a nightmare. We need to write our own sorting implementation. Well: we grab one from cflib.org anyhow. But even then, the original leverages a callback function, so I've modified this to be truly procedural and have that embedded in the implementation.

<cffunction name="monthsSortedByMaoriSequence" returntype="array" output="false">
    <cfargument name="arrayToCompare" type="array" required="true">

    <cfset var lesserArray = arrayNew(1)>
    <cfset var greaterArray = arrayNew(1)>
    <cfset var pivotArray = arrayNew(1)>
    <cfset var examine = 2>
    <cfset var comparison = 0>
    <cfset pivotArray[1] = arrayToCompare[1]>

    <cfif  arrayLen(arrayToCompare) LT 2>
        <cfreturn arrayToCompare>
    </cfif>

    <cfset arrayDeleteAt(arrayToCompare, 1)>
    <cfloop array="#arrayToCompare#" item="element">
        <cfset comparison = element.miSequence - pivotArray[1].miSequence>

        <cfswitch expression="#sgn(comparison)#">
            <cfcase value="-1">
                <cfset arrayAppend(lesserArray, element)>
            </cfcase>
            <cfcase value="0">
                <cfset arrayAppend(pivotArray, element)>
            </cfcase>
            <cfcase value="1">
                <cfset arrayAppend(greaterArray, element)>
            </cfcase>
        </cfswitch>
    </cfloop>

    <cfif arrayLen(lesserArray)>
        <cfset lesserArray = monthsSortedByMaoriSequence(lesserArray)>
    <cfelse>
        <cfset lesserArray = arrayNew(1)>
    </cfif>

    <cfif arrayLen(greaterArray)>
        <cfset greaterArray = monthsSortedByMaoriSequence(greaterArray)>
    <cfelse>
        <cfset greaterArray = arrayNew(1)>
    </cfif>

    <cfset arrayAppend(lesserArray, pivotArray, true)>
    <cfset arrayAppend(lesserArray, greaterArray, true)>

    <cfreturn lesserArray>
</cffunction>
<cfset sorted = monthsSortedByMaoriSequence(months)>

It's hard to see the bit that the modern implementation needs, but it's buried here.

Note: to an clever clogs who spot the odd shortcoming in that implementation of quicksort: you're missing the point of the article, and also yer talking to the wrong person because I didn't write it. But - yes yes - you're very clever.

The point is: that's awful. Writing old-school tag-based procedural code one needs to re-implement (and re-test!) the sorting function every time you need one. This is an extreme example and only a lunatic would not use the callback approach even with tag based code:

<cffunction name="comparator">
    <cfargument name="e1">
    <cfargument name="e2">
    <cfreturn e1.miSequence - e2.miSequence>
</cffunction>

<cfset sorted = duplicate(months)>
<cfset arraySort(sorted, comparator)>

But still: it's just better to get with the programme (or the decade) and use the modern version for this.

Righto.

--
Adam

Tuesday 29 June 2021

CFML higher-order functions compared to tag-based code: filter function

G'day

This one will be pretty short I think. It's the next effort in going over how these higher-order functions work compared to writing procedural code in CFML tags. I've previous covered map and reduce. There's less intricacy to filter, so I won't have so much to say.

Yesterday I showed an example of how not to remove records from a collection using reduce

numbers = [1,2,3,4,5,6,7,8,9,10]
evens = numbers.reduce((evens=[], number) => number MOD 2 ? evens : evens.append(number))

This works, but it's not how one ought to do it. It's putting a square peg in a round hole, and it's gonna cause a small amount of FUD when someone comes back to review the code later ("why are they using reduce here? What am I missing?"). So… use the correct tool for the job. The idiomatic way to filter our elements from a collection is with a filter operation. Here's the equivalent operation using filter:

evens = numbers.filter((number) => number MOD 2 == 0)

Filter's callback receive the value of the collection element (and additionally its index/key, as well as the whole collection as additional parameters, if you need to use those too). If the logic in the callback returns true? The element is preserved in the result collection. if it's false? It's filtered out. That's it. The callback logic can be a one-liner like it is here, or as convoluted as it needs to be. As long as it boils down to a true or a false, you'll get your filtered collection. As with the other collection higher-order functions: it does not change the original collection; it returns a new one.

The tag-based equivalent is simple:

<cfset evens = []>
<cfloop array="#numbers#" item="number">
    <cfif number MOD 2 EQ 0>
        <cfset arrayAppend(evens, number)>
    </cfif>
</cfloop>

Just slightly more verbose, and it's mostly boilerplate.

The concept here is simple, and the object of the exercise for these articles is to just show the difference between using the higher-order functions and using a procedural approach with tags, and that's pretty much it.

Righto.

--
Adam

Monday 28 June 2021

CFML: function expression syntax

G'day:

Just super quickly. One of the newer feaures in CFML is that it now supports arrow-function syntax for function expressions. I say "newer". They were apparently added to ColdFusion in 2018, and they're in Lucee: I dunno from what version.

I've been using arrow functions a bit in my code recently, cos they're just less typing for no loss of clarity compared to function expressions using the function operator. in case yer not used to them, these two code snippets are functionally equivalent:

adder = function(operand1, operand2) {
    return operand1 + operand2
}


adder = (operand1, operand2) => {
    return operand1 + operand2
}

Arrow functions offer some shortcuts though. If the body of the function is a single expression and it's the returned value, then one doesn't need to specify the block braces, or the return keyword. So the above arrow function could simply be:

adder = (operand1, operand2) => operand1 + operand2

What's more, if the function expression has only parameter, then one doesn't even need to specify the parentheses:

double = operand1 => operand1 * 2

(this is currently broken on Lucee: https://luceeserver.atlassian.net/browse/LDEV-2417).

There's no tag-based equivalent of either syntax for function expressions. That said, if one does not care about the closure side of things that function expressions utilise (and neither of these examples do), then the two function expressions above are equivalent to these two <cffunction>-based function statement declarations:

<cffunction name="adder">
    <cfargument name="operand1">
    <cfargument name="operand2">
    <cfreturn operand1 + operand2>
</cffunction>
<cffunction name="doubLe">
    <cfargument name="operand1">
    <cfreturn operand1 * 2>
</cffunction>

In reality though, these are closer to these equivalent statements in CFScript:

function adder(operand1, operand2) {
    return operand1 + operand2
}

function double(operand1) {
    return operand1 * 2
}

The difference is that these are statements, not expressions.

OK. That's enough bloody CFML tags for one evening.

Righto.

--
Adam

CFML higher-order functions compared to tag-based code: reduce function

G'day:

Here's the next effort in going over how these higher-order functions work compared to writing procedural code in CFML tags. The previous one was "CFML higher-order functions compared to tag-based code: map function". Today I'm looking at the reduce method. As per yesterday, I've discussed this before in ColdFusion 11: .map() and .reduce().

So what does reduce to? It helps if we compare it to map. Remember how I said this yesterday:

A mapping operation takes one collection and remaps the values for each key into a different value. The keys and the overall size and order (if it has a sense of order) of the collection is preserved. Also the original collection is not altered; an entirely new collection is returned.

A reduce operation is used to return a different data structure. It doesn't mean "reduce" in the sense of "make smaller"; the resultant data structure might be "bigger" (for some definition of bigger). Or it might be the same length, but a different type.

An example of returning the same length but different type would be similar to yesterday's example of mapping an array of records to an array of objects:

records = [
    {id=1, mi="whero", en="red"},
    {id=2, mi="kakariki", en="green"},
    {id=3, mi="kikorangi", en="blue"}
]
objects = records.map((record) => new Colour(record.id, record.mi, record.en))

A more likely scenario in CFML is for the records to be a query. But one still wants to pass an array of objects back from the storage tier to the application, so we use reduce to make the type conversion:

records = queryNew(
    "id,mi,en",
    "integer,varchar,varchar",
    [
        [1, "whero", "red"],
        [2, "kakariki", "green"],
        [3, "kikorangi", "blue"]
    ]
)
objects = records.reduce((objects=[], record) => objects.append(new Colour(record.id, record.mi, record.en)))

Note the way reduce works. The first argument is an "accumulator" that is passed into every iteration, and is ultimately returned to the calling code. One builds the return value iteration at a time into that. Here I'm appending to the array of objects each iteration. Whatever is returned from each iteration is the first argument of the next iteration. So as I iterate over the query, I start with an empty array. I append the first object to it, and that one-element array is then passed into the accumulator of the second call to the callback in the next iteration; and so on for all iterations so ultimately I have an array that I've appended three objects to. Some pseudo-code might make this more clear. Let's consider the iterations as they progress:

1: objects argument=[]; append Red; return value=[Red]
2: objects argument=[Red]; append Green; return value=[Red, Green]
3: objects argument=[Red, Green]; append Blue; return value=[Red, Green, Blue]
result: [Red, Green, Blue]

We start empty, we append red, we append green, we append blue.

After that first argument, the subsequent arguments follow the same pattern as with map: the second argument is a row of the query (passed as a struct). The callback can also receive the current index / key (or currentRow equivalent to a query loop in this case), and the last argument is the entire query. I don't need these here, so do not mention them in the callback's function signature.

The tag version of this is actually round about the same amount of code (109 bytes vs 112 bytes it seems):

<cfset objects = []>
<cfloop query="records">
    <cfset arrayAppend(objects, new Colour(id, mi, en))>
</cfloop>

Another case is shown here:

transactions = [
    {id=1, amount=.1},
    {id=2, amount=2.2},
    {id=3, amount=33.3},
    {id=4, amount=44.44}
]

sum = transactions.reduce((sum=0, transaction) => sum += transaction.amount)

We're summing the transactions. We are reducing the collection to a single value, I guess.

Oh one thing maybe work making very clear: it's complete coincidence that the final variable is called sum, and the accumulator parameter is called sum. They don't need to be, it just makes sense to me to match them up given we're kinda building the end result in that accumulator argument, and accordingly it's going to be the same sort of values, so makes sense it's called the same thing.

The tag-based version for this is simple again:

<cfset sum = 0>
<cfloop array="#transactions#" item="transaction">
    <cfset sum = sum += transaction.amount>
</cfloop>

Another more complicated example of script-vs-tags when reducing is in yesterday's article "CFML: tag-based versions of some script-based code". There I am reducing a query to a struct, then reducing that struct into another query. Both CFScript and tag versions of the code are in that.


One thing to not use reduce for is to actually reduce the size of a collection by removing records from it, eg:

numbers = [1,2,3,4,5,6,7,8,9,10]
evens = numbers.reduce((evens=[], number) => number MOD 2 ? evens : evens.append(number))

One would not use reduce for that. One would use filter. I guess I'll get to that tomorrow.

Righto.

--
Adam

CFML higher-order functions compared to tag-based code: map function

G'day:

As I mentioned yesterday ("CFML: tag-based versions of some script-based code") I've been asked by a couple of people to show the tag-based version of the script-based CFML code. This has ben particularly in reference to my typical approach of using higher-order functions to perform data transformation operations on iterable objects (eg: arrays, structs, lists, etc). Here I will briefly do that for some examples of using mapping functions. The process is the same each time, so I'll not dwell on it too much.

I have already written about the nuts and bolts of mapping higher-order functions in CFML back in 2014 in "ColdFusion 11: .map() and .reduce()". I also looked at how to implement arrayMap in older versions of CFML: "arrayMap(): a reverse CFML history".

In short, these collection-iteration higher order functions work on the premise that most looping operations exist solely to perform data transformation, and it makes sense to encapsulate that into a function, rather than having to hand-crank it. Obviously every data transformation is specific to its circumstance, so the collection-iteration functions take a callback as an argument (thus making them higher-order functions), where the callback defines the data transformation operation. Taking this approach makes the code clearer as to what the intent of the transformation is, and also encapsuates the implementation in its own functions, so its variables are all well encapsulated and don't impact the rest of the calling code. It's just a tider way of doing data transformation.

A mapping operation takes one collection and remaps the values for each key into a different value. The keys and the overall size and order (if it has a sense of order) of the collection is preserved. Also the original collection is not altered; an entirely new collection is returned.

That's enough of an explanation. This article is about comparing code styles. Here goes.

keys = ["ONE", "TWO", "THREE", "FOUR"]

translationLookup = {
    "ONE" = {mi = "tahi", jp = "一"},
    "TWO" = {mi = "rua", jp = "二"},
    "THREE" = {mi = "toru", jp = "三"},
    "FOUR" = {mi = "wha", jp = "å››"}
}


maori = keys.map((key) => translationLookup[key].mi)

writeDump(maori)

Here we have a one-liner that takes an array of translation keys and maps them to their actual translations.

Equivalent tag-based code is a bit more effort. We need to hand-crank our array construction:

<cfset japanese = []>
<cfloop array="#keys#" item="key">
    <cfset arrayAppend(japanese, translationLookup[key].jp)>
</cfloop>
<cfdump var="#japanese#">

In the next example I am being less literal about the "key mapping" idea, in case one got a sense that that sort of thing is inate to a mapping operation. I'm doubling each element in the array:

values = [1, 22, 333, 4444]
doubled = values.map((n) => n*2)
writeDump(doubled)

And the tags version (although here I'm halvig the values, for the hell of it). Same as the previous exercise really: just a wee bit clunkier than using the dedicated mapping function:

<cfset halved = []>
<cfloop array="#values#" item="value">
    <cfset arrayAppend(halved, value / 2)>
</cfloop>
<cfdump var="#halved#">

A more real-world example would be when yer getting an array of raw data values back from some sort of data-retrieval operation, and you want to properly model those as objects before returning them to your business logic:

records = [
    {id=1, mi="whero", en="red"},
    {id=2, mi="kakariki", en="green"},
    {id=3, mi="kikorangi", en="blue"}
]
objects = records.map((record) => new Colour(record.id, record.mi, record.en))

vs:

<cfset objects = []>
<cfloop array="#records#" item="record">
    <cfset arrayAppend(objects, new Colour(record.id, record.mi, record.en))>
</cfloop>
<cfdump var="#objects#">

You get the idea.

To show how strings can be remapped too, I knocked-together a quick example of String.map, but then remembered Lucee does not support String.map yet, so needed to use a list instead:

s = "The Quick Brown Fox Jumps Over The Lazy Dog"

a = asc("a")
z = asc("z")

rot13 = s.listToArray("").map((c) => {
    var checkCode = asc(lcase(c))

    if (checkCode < a || checkCode > z) {
        return c
    }
    var offset = (checkCode + 13) <= z ? 13 : -13

    return chr(asc(c) + offset)
}).toList("")
writeOutput(rot13)

And I tested this by feeding the result back into a tag-based version of the operation, to make sure it returned to the original string:

<cfset a = asc("a")>
<cfset z = asc("z")>

<cfset s2 = "">
<cfloop array="#listToArray(rot13, "")#" item="c">
    <cfset checkCode = asc(lcase(c))>

    <cfif checkCode LT a OR checkCode GT z>
        <cfset s2 &= c>
        <cfcontinue>
    </cfif>
    <cfset offset = 13>
    <cfif checkCode + 13 GT z>
        <cfset offset = -13>
    </cfif>
    <cfset s2 &= chr(asc(c) + offset)>
</cfloop>
<cfoutput>#s2#</cfoutput>

All in all using the specific iteration function is slightly clearer as to what sort of transformation is taking place, plus it saves you from having to write the looping and assignment scaffolding that a tags-based / hand-cranked version might. Often remappings are one-liners, and it's just more readable to do it as a simple assignment epression than having to hand-crank the boilerplate looping code.

The code for this article is all munged together in public/nonWheelsTests/higherOrderFunctionsDemonstration.

I'll have a look at how reduce operations work, tomorrow.

Righto.

--
Adam

Sunday 27 June 2021

CFML: tag-based versions of some script-based code

G'day:

OMFG the things I do for my CFML community colleagues.

I've been asked by a couple of people to show the tag-based version of the script-based CFML code I have been showing as examples when helping people recently. This is so people who are less familiar with CFScript can compare the two, and perhaps get a better handle on the script code.

Editorialisation

I have not written new tag-based code in CFML in probably 15 years, other than when it's been absolutely unavoidable like back before queryExecute existed, so we still needed to use <cfquery> (and similar stuff like <cfhttp>, and what-have-you). I have maintained old tag-based code, but I've been lucky in that I've always been in the position to implement new code using modern practices.

Some CFML History

Since ColdFusion 9 was released in 2009 (that's over a decade ago, yeah?), it's been largely unnecessary to write any business logic in tag-based code, as script-based CFCs were added to the language. The only real relics of tag-only functionality were stuff like the afore-mentioned DB and external system access functionality that was all tags still. But that stuff should be hidden away in adapter CFCs anyhow, so any necessary tag-based code should be well isolated.

It has not been necessary to write CFML in tags at all since 2014 (over seven years ago), when - in ColdFusion 11 - the last bits of tag-only functionality were ported to CFScript.

The only place any tags ought to have been used since then are in views. And really these days your views should probably be being handled by a client-side framework anyhow, so - in my opinion - no new tag-based CFML code should be being written in 2021, and shouldn't have been for over half a decade now. All new CFML code should be written in CFScript. All CFML developers must be fluent in CFScript.

Reality for a lot of people

That's all good in theory, but in practice there is a lot of legacy code out there. We don't all get to choose what codebases we work on daily, and I know some CFML devs don't get to work with modern code much, so: tags it is. And this also means some devs don't get exposed to CFScript as much as they could be, so it could all seem a bit foreign to them. Fair enough.

The code

A week or so ago, I did an exercise "CFML: emulating query-of-query group-by with higher-order functions". The final version of the code for this was (tagsVsScriptDemonstrations/groupByViaCfml/ScriptVersion.cfc):

component {

    public query function groupByYearAndMonth(required query ungroupedRecords) {
        return ungroupedRecords.reduce((grouped={}, row) => {
            var y = row.settlementDate.year()
            var m = row.settlementDate.month()
            var key = "#y#-#m#"
            grouped[key] = grouped[key] ?: {stgl = 0, ltgl = 0}
            grouped[key].stgl = grouped[key].stgl + row.ShortTermGainLoss
            grouped[key].ltgl = grouped[key].ltgl + row.LongTermGainLoss

            return grouped
        }).reduce(
            (records, key, values) => {
                records.addRow({
                    month = key.listLast("-"),
                    year = key.listFirst("-"),
                    ltgl = values.ltgl,
                    stgl = values.stgl
                })
                return records
            },
            queryNew("month,year,ltgl,stgl", "Integer,Integer,Double,Double")
        ).sort((r1, r2) => {
            var yearDiff = r1.year - r2.year
            if (yearDiff != 0) {
                return yearDiff
            }
            return r1.month - r2.month
        })
    }
}

I think a direct analogue of this in tags would be (tagsVsScriptDemonstrations/groupByViaCfml/TagsVersion.cfc)

<cfcomponent output="false">

    <cffunction name="groupByYearAndMonth" returntype="query" access="public">
        <cfargument name="ungroupedRecords" type="query" required="true">

        <cfset grouped = structNew()>
        <cfloop query="ungroupedRecords">
            <cfset var y = year(settlementDate)>
            <cfset var m = month(settlementDate)>
            <cfset var key = "#y#-#m#">

            <cfif not structKeyExists(grouped, key)>
                <cfset grouped[key] = structNew()>
                <cfset grouped[key].stgl = 0>
                <cfset grouped[key].ltgl = 0>
            </cfif>
            <cfset grouped[key].stgl = grouped[key].stgl + ShortTermGainLoss>
            <cfset grouped[key].ltgl = grouped[key].ltgl + LongTermGainLoss>
        </cfloop>

        <cfset var records = queryNew("month,year,ltgl,stgl", "Integer,Integer,Double,Double")>
        <cfloop collection="#grouped#" item="local.key">
            <cfset queryAddRow(records)>
            <cfset querySetCell(records, "month", listLast(key, "-"))>
            <cfset querySetCell(records, "year", listFirst(key, "-"))>
            <cfset querySetCell(records, "ltgl", grouped[key].ltgl)>
            <cfset querySetCell(records, "stgl", grouped[key].stgl)>
        </cfloop>
        <cfset querySort(records, sorter)>

        <cfreturn records>
    </cffunction>

    <cffunction name="sorter" returntype="numeric" access="private">
        <cfargument name="r1" required="true">
        <cfargument name="r2" required="true">

        <cfset var yearDiff = r1.year - r2.year>
        <cfif yearDiff NEQ 0>
            <cfreturn yearDiff>
        </cfif>

        <cfreturn r1.month - r2.month>
    </cffunction>

</cfcomponent>

I'm not going to go through and cross-annotate anything, but I've used analogous variable names, and kept the logic in the exact order where I could. I've also tried to keep the same level of verboseness (or lack thereof) in both examples, so that it's as true to a like-for-like as I can muster. BTW I'm also not using any member functions or other newer CFML constructs / features in these examples.


John Whish gave me a good exercise to do this morning which I'll also reproduce here. In this example we're taking an array, and deriving the two-element combinations of all the elements. For example if we start with this: ["A", "B", "C", "D", "E"], the expected result would be this: ["AB", "AC", "AD", "AE", "BC", "BD", "BE", "CD", "CE", "DE"]

In CFScript it's this (tagsVsScriptDemonstrations/combinations/ScriptVersion.cfc):

component {

    public array function getCombinations(required array array) {
        var working = duplicate(array)
        return array.reduce((combinations=[], prefix) => {
            working.deleteAt(1)
            return combinations.append(working.map((element) => "#prefix##element#"), true)
        })
    }
}

And the tag version (tagsVsScriptDemonstrations/combinations/TagsVersion.cfc):

<cfcomponent output="false">

    <cffunction name="getCombinations" returntype="array" access="public" output="false">
        <cfargument name="array" type="array" required="true">

        <cfset var working = duplicate(array)>
        <cfset var combinations = arrayNew(1)>
        <cfloop array="#array#" item="local.prefix">
            <cfset arrayDeleteAt(working, 1)>
            <cfset var subCombinations = arrayNew(1)>
            <cfloop array="#working#" item="local.element">
                <cfset arrayAppend(subCombinations, "#prefix##element#")>
            </cfloop>
            <cfset arrayAppend(combinations, subCombinations, true)>
        </cfloop>
        <cfreturn combinations>
    </cffunction>

</cfcomponent>

As a last example, I decided to see if I could port the actual test class for the combinations exercise to tags. And - yes - I could. It's really clumsy, but it works. First here's the original script version (tagsVsScriptDemonstrations/combinations/CombinationsTest.cfc):

import testbox.system.BaseSpec
import cfmlInDocker.miscellaneous.tagsVsScriptDemonstrations.combinations.ScriptVersion
import cfmlInDocker.miscellaneous.tagsVsScriptDemonstrations.combinations.TagsVersion

component extends=BaseSpec {

    function beforeAll() {
        variables.testArray = ["A", "B", "C", "D", "E"]
        variables.expectedCombinations = [
            "AB", "AC", "AD", "AE",
            "BC", "BD", "BE",
            "CD", "CE",
            "DE"
        ]
    }

    function run() {
        describe("Testing script version", () => {
            it("returns the expected combinations", () => {
                var sut = new ScriptVersion()
                var result = sut.getCombinations(variables.testArray)

                expect(result).toBe(variables.expectedCombinations)
            })
        })
        describe("Testing tags version", () => {
            it("returns the expected combinations", () => {
                var sut = new TagsVersion()
                var result = sut.getCombinations(variables.testArray)

                expect(result).toBe(variables.expectedCombinations)
            })
        })
    }
}

And the tags version (tagsVsScriptDemonstrations/combinations/CombinationsTestUsingTags.cfc):

<cfimport path="testbox.system.BaseSpec">
<cfimport path="cfmlInDocker.miscellaneous.tagsVsScriptDemonstrations.combinations.ScriptVersion">
<cfimport path="cfmlInDocker.miscellaneous.tagsVsScriptDemonstrations.combinations.TagsVersion">

<cfcomponent extends="BaseSpec" output="false">

    <cffunction name="beforeAll">
        <cfset variables.testArray = ["A", "B", "C", "D", "E"]>
        <cfset variables.expectedCombinations = [
            "AB", "AC", "AD", "AE",
            "BC", "BD", "BE",
            "CD", "CE",
            "DE"
        ]>
    </cffunction>

    <cffunction name="run">
        <cfset describe("Testing script version", testingScriptVersionHandler)>
        <cfset describe("Testing tags version", testingTagsVersionHandler)>
    </cffunction>

    <cffunction name="testingScriptVersionHandler">
        <cfset it("returns the expected combinations", returnsTheExpectedCombinationsScriptVersionHandler)>
    </cffunction>

    <cffunction name="returnsTheExpectedCombinationsScriptVersionHandler">
        <cfset var sut = new ScriptVersion()>
        <cfset var result = sut.getCombinations(variables.testArray)>

        <cfset expect(result).toBe(variables.expectedCombinations)>
    </cffunction>

    <cffunction name="testingTagsVersionHandler">
        <cfset it("returns the expected combinations", returnsTheExpectedCombinationsTagsVersionHandler)>
    </cffunction>

    <cffunction name="returnsTheExpectedCombinationsTagsVersionHandler">
        <cfset var sut = new TagsVersion()>
        <cfset var result = sut.getCombinations(variables.testArray)>

        <cfset expect(result).toBe(variables.expectedCombinations)>
    </cffunction>

</cfcomponent>

Yikes.


And indeed "yikes" was my reaction to each of those examples. The tag-based code is just full of unnecessary and obstructive bloat, and just a mess to read. And a bit clunky to implement.

Ugh. However if there's any other code I've done recently that you'd find helpful to read as tag-based code, let me know, and I'll see if I can do a port. But the quid pro quo is that if yer currently still writing CFML in tags, and have it within your control to stop doing that and join the direction CFML has been taking since mid-last-decade… please try to move on.

PS: also I'm intending to do another article that takes a more focused look on understanding how CFML's collection-iteration higher-order functions (you know; Array.map, Struct.reduce, Query.filter etc) work, and comparing back to tag-based implementations.

Righto.

--
<cfadam />

Tuesday 28 November 2017

That array_map quandary implemented in other languages

G'day:
A coupla days ago I bleated about array_map [having] a dumb implementation. I had what I thought was an obvious application for array_map in PHP, but it couldn't really accommodate me due to array_map not exposing the array's keys to the callback, and then messing up the keys in the mapped array if one passes array_map more than one array to process.

I needed to remap this:

[
    "2008-11-08" => "Jacinda",
    "1990-10-27" => "Bill",
    "2014-09-20" => "James",
    "1979-05-24" => "Winston"
]

To this:

array(4) {
  '2008-11-08' =>
  class IndexedPerson#3 (2) {
    public $date =>
    string(10) "2008-11-08"
    public $name =>
    string(7) "Jacinda"
  }
  '1990-10-27' =>
  class IndexedPerson#4 (2) {
    public $date =>
    string(10) "1990-10-27"
    public $name =>
    string(4) "Bill"
  }
  '2014-09-20' =>
  class IndexedPerson#5 (2) {
    public $date =>
    string(10) "2014-09-20"
    public $name =>
    string(5) "James"
  }
  '1979-05-24' =>
  class IndexedPerson#6 (2) {
    public $date =>
    string(10) "1979-05-24"
    public $name =>
    string(7) "Winston"
  }
}

Note how the remapped object also contains the original key value. That was the sticking point. Go read the article for more detail and more whining.

OK so my expectations of PHP's array higher order functions are based  on  my experience with JS's and CFML's equivalents. Both of which receive the key as well as the value in all callbacks. I decided to see how other languages achieve the same end, and I'll pop the codee in here for shits 'n' giggles.


CFML

Given most of my history is as a CFML dev, that one was easy.

peopleData = ["2008-11-08" = "Jacinda", "1990-10-27" = "Bill", "2014-09-20" = "James", "1979-05-24" = "Winston"]

people = peopleData.map((date, name) => new IndexedPerson(date, name))

people.each((date, person) => echo("#date# => #person#<br>"))

Oh, this presupposes the IndexedPerson component. Due to a shortcoming of how CFML works, components must be declared in a file of their own:

component {

    function init(date, name) {
        this.date = date
        this.name = name
    }

    string function _toString() {
        return "{date:#this.date#; name: #this.name#}"
    }
}


But the key bit is the mapping operation:

people = peopleData.map((date, name) => new IndexedPerson(date, name))

Couldn't be simpler (NB: this is Lucee's CFML implementation, not ColdFusion's which does not yet support arrow functions).

The output is:


2008-11-08 => {date:2008-11-08; name: Jacinda}
1990-10-27 => {date:1990-10-27; name: Bill}
2014-09-20 => {date:2014-09-20; name: James}
1979-05-24 => {date:1979-05-24; name: Winston}

Also note that CFML doesn't have associative arrays, it has structs, so the keys are not ordered. This does not matter here. (Thanks to Zac for correcting me here: CFML does have ordered structs these days).


JS

The next language I turned to was JS as that's the I'm next most familiar with. One thing that hadn't occurred to me is that whilst JS's Array implementation has a map method, we need to use an object here as the keys are values not indexes. And whilst I knew Objects didn't have a map method, I didn't know what the equivalent might be.

Well it turns out that there's no real option to use a map here, so I needed to do a reduce on the object's entries, Still: it's pretty terse and obvious:

class IndexedPerson {
    constructor(date, name) {
        this.date = date
        this.name = name
    }
}

let peopleData = {"2008-11-08": "Jacinda", "1990-10-27": "Bill", "2014-09-20": "James", "1979-05-24": "Winston"}

let people = Object.entries(peopleData).reduce(function (people, personData) {
    people.set(personData[0], new IndexedPerson(personData[0], personData[1]))
    return people
}, new Map())

console.log(people)

This returns what we want:

Map {
  '2008-11-08' => IndexedPerson { date: '2008-11-08', name: 'Jacinda' },
  '1990-10-27' => IndexedPerson { date: '1990-10-27', name: 'Bill' },
  '2014-09-20' => IndexedPerson { date: '2014-09-20', name: 'James' },
  '1979-05-24' => IndexedPerson { date: '1979-05-24', name: 'Winston' } }

TBH I think this is a misuse of an object to contain basically an associative array / struct, but so be it. It's the closest analogy to the PHP requirement. I was able to at least return it as a Map, which I think is better. I tried to have the incoming personData as a map, but the Map prototype's equivalent of entries() used above is unhelpful in that it returns an Iterator, and the prototype for Iterator is a bit spartan.

I think it's slightly clumsy I need to access the entries value via array notation instead of some sort of name, but this is minor.

As with all my code, I welcome people showing me how I should actually be doing this. Post a comment. I'm looking at you Ryan Guill ;-)

Java

Next up was Java. Holy fuck what a morass of boilterplate nonsense I needed to perform this simple operation in Java. Deep breath...

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

class IndexedPerson {
    String date;
    String name;
    
    public IndexedPerson(String date, String name) {
        this.date = date;
        this.name = name;
    }
    
    public String toString(){
        return String.format("{date: %s, name: %s}", this.date, this.name);
    }
}

class Collect {

    public static void main(String[] args) {

        HashMap<String,String> peopleData = loadData();

        HashMap<String, IndexedPerson> people = mapToPeople(peopleData);
            
        dumpIdents(people);
    }
    
    private static HashMap<String,String> loadData(){
        HashMap<String,String> peopleData = new HashMap<String,String>();
        
        peopleData.put("2008-11-08", "Jacinda");
        peopleData.put("1990-10-27", "Bill");
        peopleData.put("2014-09-20", "James");
        peopleData.put("1979-05-24", "Winston");
        
        return peopleData;
    }
    
    private static HashMap<String,IndexedPerson> mapToPeople(HashMap<String,String> peopleData) {
        HashMap<String, IndexedPerson> people = (HashMap<String, IndexedPerson>) peopleData.entrySet().stream()
            .collect(Collectors.toMap(
                e -> e.getKey(),
                e -> new IndexedPerson(e.getKey(), e.getValue())
            ));
            
        return people;
    }
    
    private static void dumpIdents(HashMap<String,IndexedPerson> people) {
        for (Map.Entry<String, IndexedPerson> entry : people.entrySet()) {
            System.out.println(String.format("%s => %s", entry.getKey(), entry.getValue()));
        }
    }
    
}

Result:
1979-05-24 => {date: 1979-05-24, name: Winston}
2014-09-20 => {date: 2014-09-20, name: James}
1990-10-27 => {date: 1990-10-27, name: Bill}
2008-11-08 => {date: 2008-11-08, name: Jacinda}

Most of that lot seems to be just messing around telling Java what types everything are. Bleah.

The interesting bit - my grasp of which is tenuous - is the Collectors.toMap. I have to admit I derived that from reading various Stack Overflow articles. But I got it working, and I know the general approach now, so that's good.

Too much code for such a simple thing though, eh?


Groovy

Groovy is my antidote to Java. Groovy makes this shit easy:

class IndexedPerson {
    String date
    String name

    IndexedPerson(String date, String name) {
        this.date = date;
        this.name = name;
    }

    String toString(){
        String.format("date: %s, name: %s", this.date, this.name)
    }
}

peopleData = ["2008-11-08": "Jacinda", "1990-10-27": "Bill", "2014-09-20": "James", "1979-05-24": "Winston"]

people = peopleData.collectEntries {date, name -> [date, new IndexedPerson(date, name)]}

people.each {date, person -> println String.format("%s => {%s}", date, person)}

Bear in mind that most of that is getting the class defined, and the output. The bit that does the mapping is just the one line in the middle. That's more like it.

Again, I don't know much about Groovy… I had to RTFM to find out how to do the collectEntries bit, but it was easy to find and easy to understand.

I really wish I had a job doing Groovy.

Oh yeah, for the sake of completeness, the output was thus:

2008-11-08 => {date: 2008-11-08, name: Jacinda}
1990-10-27 => {date: 1990-10-27, name: Bill}
2014-09-20 => {date: 2014-09-20, name: James}
1979-05-24 => {date: 1979-05-24, name: Winston}


Ruby

Ruby's version was pretty simple too as it turns out. No surprise there as Ruby's all about higher order functions and applying blocks to collections and stuff like that.

class IndexedPerson

    def initialize(date, name)
        @date = date
        @name = name
    end

    def inspect
        "{date:#{@date}; name: #{@name}}\n"
    end
end

peopleData = {"2008-11-08" => "Jacinda", "1990-10-27" => "Bill", "2014-09-20" => "James", "1979-05-24" => "Winston"}

people = peopleData.merge(peopleData) do |date, name|
    IndexedPerson.new(date, name)
end

puts people

Predictable output:

{"2008-11-08"=>{date:2008-11-08; name: Jacinda}
, "1990-10-27"=>{date:1990-10-27; name: Bill}
, "2014-09-20"=>{date:2014-09-20; name: James}
, "1979-05-24"=>{date:1979-05-24; name: Winston}
}

I wasn't too sure about all that block nonsense when I first started looking at Ruby, but I quite like it now. It's easy to read.


Python

My Python skills don't extend much beyond printing G'day World on the screen, but it was surprisingly easy to google-up how to do this. And I finally got to see what Python folk are on about with this "comprehensions" stuff, which I think is quite cool.

class IndexedPerson:
    def __init__(self, date, name):
        self.date = date
        self.name = name

    def __repr__(self):
        return "{{date: {date}, name: {name}}}".format(date=self.date, name=self.name)

people_data = {"2008-11-08": "Jacinda", "1990-10-27": "Bill", "2014-09-20": "James", "1979-05-24": "Winston"}

people = {date: IndexedPerson(date, name) for (date, name) in people_data.items()}

print("\n".join(['%s => %s' % (date, person) for (date, person) in people.items()]))


And now that I am all about Clean Code, I kinda get the "whitespace as indentation" thing too. It's clear enough if yer code is clean in the first place.

The output of this is identical to the Groovy one.

Only one more then I'll stop.

Clojure

I can only barely do G'day World in Clojure, so this took me a while to work out. I also find the Clojure docs to be pretty impentrable. I'm sure they're great if one already knows what one is doing, but I found them pretty inaccessible from the perspective of a n00b. It's like if the PHP docs were solely the user-added stuff at the bottom of each docs page. Most blog articles I saw about Clojure were pretty much just direct regurgitation of the docs, without much value-add, if I'm to be honest.

(defrecord IndexedPerson [date name])

(def people-data (array-map "2008-11-08" "Jacinda" "1990-10-27" "Bill" "2014-09-20" "James" "1979-05-24" "Winston"))

(def people
  (reduce-kv
    (fn [people date name] (conj people (array-map date (IndexedPerson. date name))))
    (array-map)
    people-data))

(print people)

The other thing with Clojure for me is that the code is so alien-looking to me that I can't work out how to indent stuff to make the code clearer. All the examples I've seen don't seem very clear, and the indentation doesn't help either, I think. I guess with more practise it would come to me.

It seems pretty powerful though, cos there's mot much code there to achieve the desired end-goal.

Output for this one:

{2008-11-08 #user.IndexedPerson{:date 2008-11-08, :name Jacinda},
1990-10-27 #user.IndexedPerson{:date 1990-10-27, :name Bill},
2014-09-20 #user.IndexedPerson{:date 2014-09-20, :name James},
1979-05-24 #user.IndexedPerson{:date 1979-05-24, :name Winston}}


Summary

This was actually a very interesting exercise for me, and I learned stuff about all the languages concerned. Even PHP and CFML.

I twitterised a comment regarding how pleasing I found each solution:


This was before I did the Clojure one, and I'd slot that in afer CFML and before JS, making the list:
  1. Python
  2. Ruby
  3. Groovy
  4. CFML
  5. Clojure
  6. JS
  7. PHP
  8. Java

Python's code looks nice and it was easy to find out what to do. Same with Ruby, just not quite so much. And, really same with Groovy. I could order those three any way. I think Python tips the scales slightly with the comprehensions.

CFML came out suprisingly well in this, as it's a bloody easy exercise to achieve with it.

Clojure's fine, just a pain in the arse to understand what's going on, and the code looks a mess to me. But it does a lot in little space.

JS was disappointing because it wasn't nearly so easy as I expected it to be.

PHP is a mess.

And - fuck me - Java. Jesus.

My occasional reader Barry O'Sullivan volunteered some input the other day:


Hopefully he's still up for this, and I'll add it to the list so we can have a look at that code too.

Like I said before, if you know a better or more interesting way to do this in any of the languages above, or any other languages, make a comment and post a link to a Gist (just don't put the code inline in the comment please; it will not render at all well).

I might have another one of these exercises to do soon with another puzzle a friend of mine had to recently endure in a job-interview-related coding test. We'll see.

Righto.

--
Adam

Saturday 19 August 2017

Mishmash: code review as a learning exercise, loops vs higher-order-functions and testing

G'day:
There's a few things going on in my head here, and none individually are worthy of an article, but perhaps if I dwell on a bunch of stuff something might shake out. I have no idea as I have not drafted this one in my head like I usually do, I'm just "let's start typing and see if I get anywhere". Eek.

OK so yesterday I was code reviewing one of my mates' work, and breezed past this code (this is not the exact code, but it's a replication of it):

public function getSomeThings($rowsToGet)
{
    $thingsValues = $this->dao->getThingsFromStorage($rowsToGet);
    
    $things = [];
    foreach ($thingsValues as $thingValues) {
        $things[] = $this->thingFactory->createFromRawData($thingValues);
    }

    return $things;
}

I just put a note in the pull request "array_map?".

Now I do a few things in a code review. Firstly I check for obvious flaws, unclean code, bad test coverage, typos, etc, and I will raise those as "tasks" in BitBucket: stuff that needs to be addressed before I'll press the "Approved" button. This is our standard approach to code review, everyone expects it, and it's fine. It works.

But secondly I'll just make observations about the code, ask questions, suggest alternative approaches we could have taken, etc. Just as food for thought, a discussion point, or a chance for some learning. This was the latter. I was not suggesting to my mate they needed to change their code to match my whim... the code is fine as it is. It was just "did you think of this?".

An interesting conversation ensued, which I will paraphrase here. Note that Fred and I do not work in the same physical office.

Fred (his name's not Fred): I'm not sure the benefit of swapping simple code for a more complex solution, unless there's some performance gain I'm unaware of.

Adam (my name is Adam): Hmmm... well array_map is one expression, and it reduces the complexity here to a single variable assignment statement(*). The loop version requires building the array by hand, and requires two assignments and the whole loop thing too. So I think the array_map approach is less complex innit?

return array_map(function ($thingValues) {
    return $this->thingFactory->createFromRawData($thingValues);
}, $thingsValues);

(*) OK it's two statements. Never let it be said I don't hyperbolise to better make my point.

One thing I did not get around to saying is that performance is not a consideration here. One of the calls is hitting an external DB, so any processing time differences are going to be eclipsed - by orders of magnitude - by the external call. And, yes, I know that if we'd be inclined to worry about micro-optimisations, then calling a higher-order function is slower than a loop. One should simply not care.

Adam (cont'ed): further more the array_map version only requires a single test, whereas the loop version needs three. That in itself demonstrates the increased complexity.

Fred: err... why's that then?

I'll break away from the conversation here.

The dev process here is that we identify the need to have a function to get stuff from the DB, and then model that and return it to the calling code. So doing TDD we write a test which uses a mocked-out DAO to return known data, and we test that the values in the resultant models are what we expected them to be based on the mocked DAO data.

Here's a very simplified, self-contained example:

First our test:

describe('comparing testing for foreach vs array_map', function() {
    given('sut', function () {
        return new ForeachVsArrayMap();
    });
    given('allObjects', function () {
        return [
            (object) ['id' => 1, 'mi' => 'tahi'],
            (object) ['id' => 2, 'mi' => 'rua'],
            (object) ['id' => 3, 'mi' => 'toru'],
        ];
    });
    describe('tests of foreach', function() {
        it('returns the numbers as objects', function () {
            $result = $this->sut->getObjects();
            expect($result)->toEqual($this->allObjects);
        });
    });
});

And now our implementation:

class ForeachVsArrayMap
{
    private $allData;

    public function __construct()
    {
        $this->allData = [
            ['id' => 1, 'mi' => 'tahi'],
            ['id' => 2, 'mi' => 'rua'],
            ['id' => 3, 'mi' => 'toru']
        ];
    }

    private function getNumbers($rows)
    {
        return array_slice($this->allData, 0, $rows);
    }

    public function getObjects($rows = 3)
    {
        $data = $this->getNumbers($rows);
        $numbers = [];
        foreach ($data as $row) {
            $numbers[] = (object) $row;
        }

        return $numbers;
    }
}

And that passes. That's our TDD side of things done.

However we also now have to do edge testing as well. We've got a loop in there, and all loops need tests for three things: zero iterations, one iteration, and more than one iterations. There are easily introduced logic errors around the plurality of loop iteration.

Here our TDD test has coverd the "more than one iterations" case, so we only need the other two:

it('handles one result', function () {
    $result = $this->sut->getObjectsUsingForeach(1);
    $expected = [$this->allObjects[0]];

    expect($result)->toEqual($expected);
});

it('handles zero results', function () {
    $result = $this->sut->getObjectsUsingForeach(0);
    $expected = [];

    expect($result)->toEqual($expected);
});

Pretty simple tests, but still: two additional tests.

Why don't we need these two tests when using array_map? Because array_map's iteration is all handled within its implementation. It's PHP's job to test that, not ours. So all we need to test is that the inline callback works. And we only need to test that once: when it's called, it does what we expect it to do. We can safely stipulate that array_map will always return an array with the same number of elements as the input value. PHP deals with that; we don't need to.

One good thing about TDD (and test coverage in general) is now we can safely swap in the array_map version, and the test coverage still passes, so we know - with a high degree of certainty - that our code is A-OK.

public function getObjects($rows = 3)
{
    return array_map(function ($row) {
        return (object) $row;
    }, $this->getNumbers($rows));
}

Coincidentally I had another conversation, in another code review, on Friday about the necessity of always testing loops for 0, 1, >1 iterations. "One can just look at the code and know it's fine". This statement didn't come up in the conversation, but I've heard it before.

Three weeks ago I was show-stopped by a bug in one of our libraries. Taking the analogy above, this was the code:

public function getObjectsUsingForeach($rows = 3)
{
    $data = $this->getNumbers($rows);

    foreach ($data as $row) {
        $numbers[] = (object) $row;
    }

    // do something else with $numbers here
    // ...
}

The code had no tests. Can you see what's wrong? The general expectation when calling a function to get data is that there's actually data to get, given the provided criteria. In the case I fell foul of, the value of $rows was zero. And this was legit. So if $rows is 0, what value does $numbers have after the loop? Well: $numbers was undefined.

Had the dev done their testing properly, then they'd've noticed their bug. They never initialised $numbers outside of the code in the loop, so there's a chance it might never get defined. In this case not only did the dev not do the edge-case testing, they didn't even do the baseline TDD testing either, but that's a different story.

I needed to down-tools and fix the library before I could continue my own work. And it was made very tricky by the fact the code in the library had no tests at all, and wasn't written in a clean-code fashion (so was basically untestable). But we are not allowed to work that way any more, so I needed to do a bunch of work the original developer ought to have done.

I have also fell foul of this brainfart too:

public function getObjectsUsingForeach($rows = 3)
{
    $data = $this->getNumbers($rows);

    foreach ($data as $row) {
        $numbers = [];
        $numbers[] = (object) $row;
    }

    // do something else with $numbers here
    // ...
}

This usually crops up when code that was previously used once suddenly needs to be used for multiple iterations. Note that the initialisation of $numbers is inisde the loop, so it will re-initialise every iteration. This would be caught by the "more than one iterations" test. This is rarer, and usually only crops up in completely untested code: it'd be a shit tester who tested a loop with only one iteration as the test, but I've seen it done. Saves time not having to mock so much data, you see? Berks.

So, anyhow... always test yer loops. And try to avoid loops if there are built-in functions that handle the iteration for you.

Also always do TDD, but also understand that there is more testing to do after the TDD cycle. TDD is not the end of the story there.



In the title of this article I mention "code review as a learning exercise". I greatly summarised the back and forth between Fred and myself here... there was an order of magnitude more discussion than there was code under discussion. this sort of interaction is gold. Some people get the idea that code review is kinda like a teacher marking homework. It's partly that, but that's only part. As programmers and coders our job is one of communication: communication of instructions to a computer, both to the computer itself, but also to our future colleagues and selves about the nature of the instructions. Programming languages are a means to communicate instructions to other humans. It's tangential the computer can also be made to understand them. Similarly reviewing each other's code should be a communications and collaboration process, and we absolutely should be having these discussions. Both Fred and I benefited from this conversation: a lot of what I typed here wasn't formalised in my brain before explaning it, but now I have a much clearly picture of what I do and why. And Fred knows too. This was not a waste of time, as we're both now better developers for it. And it might have seemed like a lot of back and forth in the code review, but it really only represents about 10min of discussion. And all this came from just a conversation point, not me saying "change the code".

If we were in the same office, we might have had the conversation via voice instead of typing in the code review, and some people might think that's better. In a way it is, but it also means that the details of the conversation don't get recorded, so benefit fewer people. That said, when programmers are not agreeing on things, often it is better to switch to voice instead of continue the discussion via text-based back-and-forth. It's just easier and more collaborative sometimes.

Right, that's that. A bit random, but didn't turn out so bad I think. And I seem to have found a point to make.

Righto.

--
Adam

Friday 24 March 2017

Help explain closure to the Adobe ColdFusion Team

G'day:
OK, this is a CFML-centric article. But it's also a call to provide code analogies in different languages to a CFML example, to show the ColdFusion Team they dunno what they're on about.

Here's some CFML code:

function doit() {
    var a = ["a", "b", "c"];
    var b = ["x", "y", "z"];
    var counter = 0;
    
    arrayEach(a, function(foo) {
        counter = 0;
        arrayEach(b, function(bar) {
            counter++;
            // in dump counter is always 0
            writeDump({
                counter: counter, 
                foo: foo, 
                bar: bar
            });
        });
    });
}
doit();

This leverages closure to reference the outer counter variable within the innermost function expression. Ergo, it's the same variable. So the expected output of this would be:


Note how the counter is declared in the main function, reset in the outer arrayEach handlers and incremented for each iteration of the inner arrayEach call. So it'll cycle through 1,2,3 three times when output.

That was run on Lucee. Running it on ColdFusion yields:


See how the counter is messed up: it's always zero. It should increment for each iteration of the inner function expression.

Adobe - being their typical selves - is claiming this is "by design":

https://tracker.adobe.com/#/view/CF-4197194:


That's nonsense.

Other languages behave predictably:

JavaScript:

function doit() {
    var a = ["a", "b", "c"];
    var b = ["x", "y", "z"];
    var counter = 0;
    
    a.forEach(function(foo) {
        counter = 0;
        b.forEach(function(bar) {
            counter++;
            // in dump counter is always 0
            console.log({
                counter: counter, 
                foo: foo, 
                bar: bar
            });
        });
    });
}
doit();
VM128:11 Object {counter: 1, foo: "a", bar: "x"}
VM128:11 Object {counter: 2, foo: "a", bar: "y"}
VM128:11 Object {counter: 3, foo: "a", bar: "z"}
VM128:11 Object {counter: 1, foo: "b", bar: "x"}
VM128:11 Object {counter: 2, foo: "b", bar: "y"}
VM128:11 Object {counter: 3, foo: "b", bar: "z"}
VM128:11 Object {counter: 1, foo: "c", bar: "x"}
VM128:11 Object {counter: 2, foo: "c", bar: "y"}
VM128:11 Object {counter: 3, foo: "c", bar: "z"}


And PHP (apologies for the rubbish way PHP does closure):

function doit() {
    $a = ["a", "b", "c"];
    $b = ["x", "y", "z"];
    $counter = 0;
    
    array_walk($a, function($foo) use (&$counter, $b) {
        $counter = 0;
        array_walk($b, function($bar) use (&$counter, $foo) {
            $counter++;
            // in dump counter is always 0
            var_dump([
                "counter" => $counter, 
                "foo" => $foo, 
                "bar" => $bar
            ]);
        });
    });
}
doit();


array(3) {
  ["counter"]=>
  int(1)
  ["foo"]=>
  string(1) "a"
  ["bar"]=>
  string(1) "x"
}
array(3) {
  ["counter"]=>
  int(2)
  ["foo"]=>
  string(1) "a"
  ["bar"]=>
  string(1) "y"
}
array(3) {
  ["counter"]=>
  int(3)
  ["foo"]=>
  string(1) "a"
  ["bar"]=>
  string(1) "z"
}
array(3) {
  ["counter"]=>
  int(1)
  ["foo"]=>
  string(1) "b"
  ["bar"]=>
  string(1) "x"
}
array(3) {
  ["counter"]=>
  int(2)
  ["foo"]=>
  string(1) "b"
  ["bar"]=>
  string(1) "y"
}
array(3) {
  ["counter"]=>
  int(3)
  ["foo"]=>
  string(1) "b"
  ["bar"]=>
  string(1) "z"
}
array(3) {
  ["counter"]=>
  int(1)
  ["foo"]=>
  string(1) "c"
  ["bar"]=>
  string(1) "x"
}
array(3) {
  ["counter"]=>
  int(2)
  ["foo"]=>
  string(1) "c"
  ["bar"]=>
  string(1) "y"
}
array(3) {
  ["counter"]=>
  int(3)
  ["foo"]=>
  string(1) "c"
  ["bar"]=>
  string(1) "z"
}


But I'm quite keen to know if there's any language that behaves as ColdFusion does in this example? If you've got a mo', could you knock out an equivalent of this code in [your language of choice], and share the results?

I'm 99.999% sure Adobe are just not quite getting closure, but wanna make sure I'm not missing anything.

Righto.

--
Adam

Saturday 3 December 2016

Thoughts and implementation on that daft FizzBuzz interview question

G'day:
I'm not quite sure where I'm gonna go with this article yet. Usually when I start an article like this it ends up being a bit rubbish, so... well sorry if it turns out this way this time too.

Right so there was a conversation on the CFML Slack channel last week stemming from someone deciding to use the old FizzBuzz question as part of a job interview. I'm not a great proponent of such things in interviews, but then back when I used to conduct such things I was shit at doing 'em anyhow, so what do I know?

But I don't think it's a sensible measure of anything to make someone code something in the actual interview: interviews can be high-stress environments, with restricted time-framing, and I don't think that reflects the general programming environment. Well not one I'd want to work in, anyhow. I've once been asked to design a database - in an interview for a developer role, I hasten to add - I just said "no, I'm not going to do that; I don't think that's a good use of anyone's time, and it casts you in a bad light for asking, and me in a bad light as I fumble around trying to do something that might normally take hours in only a few min. So: no" (that's a paraphrase, but it is the general gist of how the conversation went). I think perhaps had I actually wanted the job I might have humoured them. Maybe. It's dumb-arse anyhow.

Fortunately in this fizzbuzz situation the person conducting the interview was like-minded, and told the candidate to take it home with them and flick something back later. That's reasonable.

But what should one look for in a situation like this? And what should one deliver as a solution?

Firstly... in case you are like me and had no idea what people were on about when they started talking about FizzBuzz, this is the question:

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".
(from http://wiki.c2.com/?FizzBuzzTest)

The Slack channel got to messing around, and as one can probably imagine, shortly people starting answering the question. A typical answer might be like this:

for ($i=1; $i <=100; $i++) {
    $s = "";
    if ($i % 3 == 0) $s .= "fizz";
    if ($i % 5 == 0) $s .= "buzz";
    
    echo $s ?: $i;
}

That's simple and to the point, but I would not be impressed with that answer. Why? It's not reusable at all. OK, cool, the question didn't ask for re-use. That being the case, why bother with the code? Just use the output:

echo "12fizz4buzzfizz78fizzbuzz[etc]buzz";

If yer not needing a reusable answer... don't piss about writing code to do it. Just answer it. If I was in the position to discuss this with the interviewer - rather than just submit the answer - I would indeed consider starting with that answer there. Not to be a smart arse, but to let them know I think about that sort of thing.

But I'd then go on to point out some flaws in the question and in the answer. I think it's reasonable to provide slightly reusable code, not least of all so one can test it. One could test a file that simply has that statement in it, but it's not so orthodox to do so.

I put the answer in a function:

function getFirst100FizzBuzzNumbers(){
    return "12fizz4buzzfizz78fizzbuzz[etc]buzz";
}

Then quickly realise that's still not very testable as testing frameworks usually expect code to be in classes. So I decide to do that: put it in a class.

However another thing I'd be wanting to do in this exercise is to demonstrate "test first" as a developmental approach. For this I just decided to use PHPSpec for a change today, as I heard someone mention it and thought "yeah, all right...". To get that strummed-up I need a composer.json file:

{
    "name" : "fizzBuzzChallenge",
    "require-dev" : {
        "phpspec/phpspec" : "^3.0"
    },
    "config": {
        "bin-dir": "bin"
    },
    "autoload": {
        "psr-4" : {
            "me\\adamcameron\\fizzBuzzChallenge\\" : "src/"
        }
    }
}

(some of that's from the PHPSpec docs, the rest is just "well I'll need to put the code somewhere, so might as well PSR-4 it and namespace it etc too).

Following the PHPSpec docs still, I start by describing my spec:


C:\src\php\php.local\src\puzzle\fizzbuzz>bin\phpspec desc me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle
Specification for me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle created in C:\src\php\php.local\src\puzzle\fizzbuzz\spec\FizzBuzzPuzzleSpec.php.


C:\src\php\php.local\src\puzzle\fizzbuzz>bin\phpspec run
me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle
  11  - it is initializable
      class me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle does not exist.

                                      100%                                       1
1 specs
1 example (1 broken)
25ms


  Do you want me to create `me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle`
  for you?
                                                                         [Y/n]

Class me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle created in C:\src\php\php.local\src\puzzle\fizzbuzz\src\FizzBuzzPuzzle.php.

                                      100%                                       1
1 specs
1 example (1 passed)
27ms


C:\src\php\php.local\src\puzzle\fizzbuzz>

So that's all quite cool. Notice that it created the spec class for me, and it also created the actual class for me too.

Now I have this:

namespace spec\me\adamcameron\fizzBuzzChallenge;

use me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class FizzBuzzPuzzleSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType(FizzBuzzPuzzle::class);
    }
}


namespace me\adamcameron\fizzBuzzChallenge;

class FizzBuzzPuzzle
{
}

Now I need to spec-out my function (test first remember!):

class FizzBuzzPuzzleSpec extends ObjectBehavior {

    function its_getFirst100FizzBuzzNumbersMethodReturnsTheExpectedValue() {
        $this->getFirst100FizzBuzzNumbers()->shouldBe("12fizz4buzzfizz78fizzbuzz[etc]buzz");
    }
}


(It's not part of my requirement to spec-out the return type of the class, so I got rid of that one).

C:\src\php\php.local\src\puzzle\fizzbuzz>bin\phpspec run
me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle
  10  - its getFirst100FizzBuzzNumbersMethodReturnsTheExpectedValue
      method me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle::getFirst100FizzBuzzNumbers not found.

                                      100%                                       1
1 specs
1 example (1 broken)
227ms


  Do you want me to create
  `me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle::getFirst100FizzBuzzNumbers
  ()` for you?
                                                                         [Y/n]

  Method me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle::getFirst100FizzBuzzNumbers() has been created.

me\adamcameron\fizzBuzzChallenge\FizzBuzzPuzzle
  10  - its getFirst100FizzBuzzNumbersMethodReturnsTheExpectedValue
      expected "12fizz4buzzfizz78fizzbuzz...", but got null.

                                      100%                                       1
1 specs
1 example (1 failed)
29ms


C:\src\php\php.local\src\puzzle\fizzbuzz>

Now I have the method skeleton as well:

public function getFirst100FizzBuzzNumbers()
{
    // TODO: write logic here
}


Which I replace with my implementation:

function getFirst100FizzBuzzNumbers(){
    return "12fizz4buzzfizz78fizzbuzz[etc]buzz";
}

Which works:

C:\src\php\php.local\src\puzzle\fizzbuzz>bin\phpspec run
                                      100%                                       1
1 specs
1 example (1 passed)
31ms


C:\src\php\php.local\src\puzzle\fizzbuzz>


However it's perhaps realistic to expect this thing to actually do the work dynamically instead of just returning a hard-coded string. So I shift my specification slightly to be "a function which takes an integer i and returns the first i FizzBuzz numbers.

The simplest spec for this is to just test that it returns a FizzBuzz number. The first FizzBuzz number is 1:

function its_getFizzBuzzNumbersMethodReturnsAFizzBuzzNumber() {
    $this->getFizzBuzzNumbers(1)->shouldBe(1);
}

Again I run the tests, and again it creates my method skeleton for me:

public function getFizzBuzzNumbers($argument1)
{
    // TODO: write logic here
}


Which I replace with enough code to pass.

public function getFizzBuzzNumbers($i) {
    return $i;
}


Now I'm intending to write enough of the algorithm to get me to 3, which is the first variation of the return value. I'm also gonna change the specification to return an array not a string. This is a nod to not assuming the use of the result, and an array is a better data type for a sequence of things. Coupled with this, I want to separate out the collection (the array) from the contents of the collection (FizzBuzz numbers). So I decide to spec a different function for now: getFizzBuzzNumber.

I create a spec:

function its_getFizzBuzzNumberMethodReturnsAFizzBuzzNumber() {
    $this->getFizzBuzzNumber(1)->shouldMatch('/\d+|fizz/');
}


And create a naive implementation of that:

public function getFizzBuzzNumber($i) {
    return $i;
}


And test the spec:

  16  - its getFizzBuzzNumberMethodReturnsAFizzBuzzNumber
      no match([array:1]) matcher found for [integer:1].

Erm... damn. What? This took a while to work out. PHPSpec seems to have forgotten that PHP is a dynamic, loosely-typed language, and seems to type-check the value before testing the pattern. If I returned a string, this spec worked:

public function getFizzBuzzNumber($i) {
    return (string) $i;
}

1 specs
1 example (1 passed)

Now I almost lived with that. I almost lived with returning the numeric FizzBuzz numbers as strings to make my pattern work. But that seemed arse-about-face. The spec was getting it "wrong", not my code. I chatted on the BDDLondon Slack Channel about how to test for "this OR that" (if not able to use a regex pattern"), and the answer was "cannae". This also had me thinking "I actually really hate methods that return two different types of thing, as it seems a bit messy. Then it occurred to me... it's not returning two different types of thing. It was returning one type of thing: it was returning a FizzBuzzNumber object. Aha! I was not after a collection of a mix of integers and strings "fizz", "buzz" or "fizzbuzz". They were all just FizzBuzzNumber objects. Cool.

So I spec-ed out a second class: FizzBuzzNumber. I'll spare you the stepped spec->code->repeat, and here's the end result. Spec first:

<?php

namespace spec\me\adamcameron\fizzBuzzChallenge;

use me\adamcameron\fizzBuzzChallenge\FizzBuzzNumber;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class FizzBuzzNumberSpec extends ObjectBehavior
{
    function it_isInitializable() {
        $this->beConstructedWith(1);
        $this->shouldHaveType(FizzBuzzNumber::class);
    }

    function it_isInitializableWithFizz() {
        $this->beConstructedWith("Fizz");
        $this->shouldHaveType(FizzBuzzNumber::class);
    }

    function it_isInitializableWithBuzz() {
        $this->beConstructedWith("Buzz");
        $this->shouldHaveType(FizzBuzzNumber::class);
    }

    function it_isInitializableWithFizzBuzz() {
        $this->beConstructedWith("FizzBuzz");
        $this->shouldHaveType(FizzBuzzNumber::class);
    }

    function it_isInitializableWithOtherInteger() {
        $this->beConstructedWith(2);
        $this->shouldHaveType(FizzBuzzNumber::class);
    }

    function it_isNotInitializableWithMultipleOfThree() {
        $this->beConstructedWith(6);
        $this->shouldThrow('\InvalidArgumentException')->duringInstantiation();
    }

    function it_isNotInitializableWithMultipleOfFive() {
        $this->beConstructedWith(10);
        $this->shouldThrow('\InvalidArgumentException')->duringInstantiation();
    }

    function it_returnsFizzWhenCreatingFromIntegerWithMultipleOfThree() {
        $this->beConstructedWith(1);
        $this::fromInteger(12)->shouldHaveType(FizzBuzzNumber::class);
        $this::fromInteger(12)->__toString()->shouldBe("fizz");
    }

    function it_returnsBuzzWhenCreatingFromIntegerWithMultipleOfFive() {
        $this->beConstructedWith(1);
        $this::fromInteger(20)->shouldHaveType(FizzBuzzNumber::class);
        $this::fromInteger(20)->__toString()->shouldBe("buzz");
    }

    function it_returnsFizzBuzzWhenCreatingFromIntegerWithMultipleOfFifteen() {
        $this->beConstructedWith(1);
        $this::fromInteger(30)->shouldHaveType(FizzBuzzNumber::class);
        $this::fromInteger(30)->__toString()->shouldBe("fizzbuzz");
    }

    function it_returnsAStringFromToString() {
        $this->beConstructedWith(1);
        $this->__toString()->shouldBeString();
    }
}



(I'm writing this part of this article about a week later, and I still can't stand seeing those underscores in my method names!)

But I like what PHPSpec has made me do here: define my FizzBuzzNumber's behaviour:

  • it can be constructed with a value of 1 (a fairly baseline "it works");
  • or a value of "fizz"
  • or "buzz";
  • or "fizzbuzz";
  • or some other integer not matching those rules;
  • but not an integer multiple of three (because there's no such FizzBuzzNumber... that'd be "Fizz");
  • Likewise with a multiple of five
  • (multiple of 15 is implicit from those two).
  • However its fromInteger method can create an object from multiples of 3
  • or 5.
  • And if required to do so, returns a string value of itself (like for output).

That's a reasonable description (ie: specification) of a FizzBuzzNumber, I reckon?

The implementation is pretty simple:

<?php

namespace me\adamcameron\fizzBuzzChallenge;

class FizzBuzzNumber {

    private $value;

    function __construct($value) {
        if (!preg_match('/\d+|fizz|buzz|fizzbuzz/', $value)) {
            throw new \InvalidArgumentException("$value is not a valid FizzBuzzNumber");
        }
        if (is_integer($value) && !($value % 3 && $value % 5)){
            throw new \InvalidArgumentException("$value is not a valid FizzBuzzNumber");
        }

        $this->value = $value;
    }

    static function fromInteger($i) : FizzBuzzNumber {
        $result = "";

        if ($i % 3 == 0) {
            $result .= "fizz";
        }
        if ($i % 5 == 0) {
            $result .= "buzz";
        }
        if (empty($result)) {
            $result = $i;
        }

        return new self($result);
    }

    function __toString() {
        return (string) $this->value;
    }
}

Notes:

  • The constructor does nothing clever. It takes a valid FizzBuzzNumber value and rejects values that aren't valid. EG: 15 is not a valid FizzBuzzNumber: there's no such thing. The integer 15 might exist, but the FizzBuzzNumber equivalent is "fizzbuzz".
  • The fromInteger method now holds all the logic of converting an integer to a FizzBuzzNumber, which is pretty much what we've been asked for: take the integers 1-100 and return their FizzBuzzNumbers.
  • We want to treat FizzBuzzNumbers just like we would an integer etc. So if we output it, we just spit out its string value.
Right, so that's our numbers sorted out. Now we need a collection of them. This is pretty devoid of requirement now given the "fizz buzz" logic is within FizzBuzzNumber, so I made an arbitrary decision to take a slightly more complex route than perhaps I could have. I've decided to use a generator rather than just return the series at once. Why? This is for a job interview right, so it's perhaps good to demonstrate one's understanding of the language extends further than the basics. Also - as a discussion point - it's perhaps worthwhile to observe that this "fizz buzz" exercise is nothing more than a very naive Sieve of Eratosthenes, and that kind of lends itself to be handled via an "on demand" collection, a generator makes sense. But it's mostly one of those "because I can" sort of things. Plus it's dead easy in PHP.

Again, spec first:

<?php

namespace spec\me\adamcameron\fizzBuzzChallenge;

use me\adamcameron\fizzBuzzChallenge\FizzBuzzNumber;
use me\adamcameron\fizzBuzzChallenge\FizzBuzzSequence;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class FizzBuzzSequenceSpec extends ObjectBehavior {

    function its_class_IsInitializable() {
        $this->shouldHaveType(FizzBuzzSequence::class);
    }

    function it_returnsaGeneratorfromGetFizzBuzzSequence() {
        $this->getFizzBuzzSequence()->shouldHaveType(\Generator::class);
    }

    function it_fulfilsTheRequirement(){
        $lengthToTest = 100;
        $this->beConstructedWith($lengthToTest);

        $expected = array_map(function($i){
            return FizzBuzzNumber::fromInteger($i);
        },range(1, $lengthToTest));

        $this->getFizzBuzzSequence()->shouldIterateAs($expected);
    }
}


Notes:

  • This tests that is is a generator.
  • Plus here is the test for the actual requirement! Yup. I don't output it on the screen and ask the examiner to eyeball it manually: I provide a test for them so they can see that the computer has verified my work.

And the actual code is thus:

<?php

namespace me\adamcameron\fizzBuzzChallenge;

class FizzBuzzSequence {

    private $length;

    public function __construct($length=null) {
        $this->length = $length;
    }

    public function getFizzBuzzSequence() {
        $i = 0;
        while (is_null($this->length) || $i < $this->length){
            $i++;
            yield FizzBuzzNumber::fromInteger(($i));
        }
    }

    public function __invoke() {
        return $this->getFizzBuzzSequence();
    }
}

Simple, huh? OK, granted, not as simple as just a foreach loop, but it's not that much more complicated. The only twist is that I've just added an __invoke() method so that the object can be called as a method. Syntactical sugar.

OK, so if yer like me having that test there that proves the thing does the business ain't good enough. I also wrote a "manual test" to demonstrate it:

$seq = new FizzBuzzSequence(100);

foreach ($seq() as $fizzBuzzNumber) {
    echo $fizzBuzzNumber . PHP_EOL;
}

So we still get our simple foreach anyhow. But the implementation is hidden away in suitable classes.

And yeah it works:


C:\src\php\php.local\src\puzzle\fizzbuzz\public>php manualTest.php
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz
fizz
22
23
fizz
buzz
26
fizz
28
29
fizzbuzz
31
32
fizz
34
buzz
fizz
37
38
fizz
buzz
41
fizz
43
44
fizzbuzz
46
47
fizz
49
buzz
fizz
52
53
fizz
buzz
56
fizz
58
59
fizzbuzz
61
62
fizz
64
buzz
fizz
67
68
fizz
buzz
71
fizz
73
74
fizzbuzz
76
77
fizz
79
buzz
fizz
82
83
fizz
buzz
86
fizz
88
89
fizzbuzz
91
92
fizz
94
buzz
fizz
97
98
fizz
buzz

C:\src\php\php.local\src\puzzle\fizzbuzz\public>

This is perhaps an overly-belaboured design of something simple, but I still think the design is sound, simple, and well-tested. I think it's an easy amount of work, and demonstrates a few sensible processes that are just as important to a developer job than actual code:

  • be able to read the requirement, understand it, and deliver what's asked for (the hard-coded answer does this). This demonstrates one is paying attention and notices things.
  • But question the requirement in a sensible way: is it just a one use thing? Is "100" cast in stone, or is it a parameter? This demonstrates one can then offer insight into where the client might have overlooked something, or "under-stated" their requirements due to using casual "human speak" when saying things like "I need the first 100 FizzBuzzNumbers". Questioning is always part of one's job.
  • Any work one does should be spec'ed out (but only in a "just in time" fashion, not some horrendous waterfall). Know what yer doing before you set out to do it. It also helps collaboration with other devs, as it summarised things for them.
  • and tested. All our work needs to be tested.
  • Once one has teased-out the requirements from the initial statement, then one can crack on with the implementation, and piss around being "smart". Provided one can explain one's self.

That's the end of "my point" with this article.


As a digression though... obviously with these exercises "seasoned" devs might be wont to show-off how clever they are by making the solution as terse as they can. As if that's a good thing. It's not. But the conversation erred that way on the CFML Slack channel, and I rose to the bait.

Here's my foolishly terse solution to this, in a challenge I set for myself to implement the solution as a single expression in as fewer chars as possible:

[].set(1,100,"").reduce((s,_,i)=>s&(!i%15?"fizzbuzz":!i%3?"fizz":!i%5?"buzz":i),"")

(that only works on Lucee 5, btw. It will not work on ColdFusion)

That's absolutely ludicrous, and if anyone actually submitted that as an answer to the question they should be shot (yes, fine, a harsh penalty for failing a job interview).

But I was quite pleased that CFML can express the solution in one 84-char expression. I thought about trying to implement the same thing in PHP, but it simply can't do this sort of thing. Not being very OO and not doing a very good nod to FP doesn't help. CFML's abilities are def underrated here. In this example of "how to write shit code that'd get you shot".

Righto.

--
Adam