Showing posts with label CFML. Show all posts
Showing posts with label CFML. Show all posts

Wednesday 9 June 2021

Repro for code that breaks in ColdFusion but works in Lucee

G'day:

In the last article (CFML: messing around with mixins (part 2)) I used some code that worked in Lucee, but didn't work in ColdFusion. It was not my code's fault, it's ColdFusion's fault for not being able to parse it. I didn't think I could be arsed looking at it this evening, but someone's asked me about it already so I had a look. I can reproduce the issue, and work around it.

Here's the code wot works on Lucee but breaks on ColdFusion (on trycf.com):

obj = {
    myMethod = function () {return arguments}
}    
methodName = "myMethod"
    
useProxy = true

callMe = useProxy
    ? () => obj[methodName](argumentCollection=arguments)
    : obj[methodName]

result = callMe("someArg")
writeDump(result)

This errors with: Invalid CFML construct found on line 10 at column 8. on line 10

It's pointing to the arrow function shorthand, but if I change it back to a normal function expression using the function keyword, it still errors.

It's also not the dynamic method call either. In the past that would have caused problems on ColdFusion, but they've fixed it at some point.

It's also not the lack of semi-colons ;-)

This adjustment works (on trycf.com):

obj = {
    myMethod = function () {return arguments}
}
methodName = "myMethod"
    
useProxy = true

proxy = () => obj[methodName](argumentCollection=arguments)
callMe = useProxy
    ? proxy
    : obj[methodName]

result = callMe("someArg")
writeDump(result)

It seems like ColdFusion really didn't like that arrow function / function expression in the ?: expression.

I'd say this is definitely a bug, and will point this repro case to Adobe for them to… do whatever they like with it.

Righto

--
Adam

CFML: messing around with mixins (part 2)

G'day:

In the previous article - CFML: messing around with mixins (part 1) - I had a look at some code to implement both compile-time and runtime mixins into target objects. Basically one takes a library object (a CFC just full of functions, no statefulness or anything), and stick references to them into a target object so that object can call those methods as if they were its own native method. It was all very proof-of-concept, and I would never recommend anyone actually using the code I wrote, and indeed would never recommend using mixins at all. They just seem like a hack to me: an anti-pattern and poor design.

One drawback of the approach I had taken thus far is that the way of injecting the functions was very naïve. It would only work as a tactic if the functions were very simple, and didn't refer to anything else within their library: no state (which isn't a problem in a library situation), but also no references to other private functions within the library. This is more of a problem. The reason it won't work is that a function statement doesn't bind any context to itself, it just adopts the context of wherever it's run from. Consider this:

// Library.cfc
component {
    function doSomething() {
        // some code
        
        someValue = runPrivateFunction()
        
        // more code
    }
    
    private function runPrivateFunction() {
        // code
    }
}

And using our injector we inject doSomething in to this:

// Object.cfc
component {


    myMethod() {
        // some code
        
        doSomething()
        
        // some more code
    }
}

This won't work. We only mixin a reference to doSomething, and when it's run in the context of Object, it will try to find runPrivateFunction in that object's context, and it won't be there.

We could inject all the private methods into the target as well: that's easy enough. But what if the object we're mixing-in from isn't a simple library, but it's a stateful object, or has dependencies injected into it that its methods then expect to be able to use… we'd have to chuck everything into the target. And by this point one should be questioning what the hell one is doing. It's just rubbish.

Instead of that, I'm just going to bind it to its original context instead. So we just inject the method, but it will still know about its context from the original object it came from. And it seems to be surprisingly easy.

More test cases…

It binds mixed-in methods to their original calling context when using a mixinMap

In this example we have a model class:

// MyModel.cfc
component {

    function getObjects(orderBy="id") {
        return getObjectsFromStorage(orderBy)
    }
}

And you can see it's calling a repository-ish method to getObjectsFromStorage. It just returns those in this example, but it's the sort of thing where you have a request to return some objects, the model tier calls a repository to get the objects, then the model performs business logic on the to round out the requisite business logic, and returns them to whatever asked for them - probably a controller.

You can also se that MyModel doesn't implement that behaviour, we need to inject it as a mixin. The repo is like this:

component {

    function init(MyDao dao) {
        variables.dao = dao
    }

    function getObjectsFromStorage(orderby="id") {
        orderBy = mapOrderBy(orderBy)
        return dao.getRecords(orderBy).reduce((array=[], record) => array.append(new Number(record.mi)))
    }

    private function mapOrderBy(property) {
        propertyColumnMap = {
            "id" = "id",
            "english" = "en",
            "maori" = "mi"
        }
        return propertyColumnMap.keyExists(property) ? propertyColumnMap[property] : "id"
    }
}

This is a proper dependency-injected repository. The repo method knows how to get the DAO to fetch the raw storage data, and it knows how to convert those records into domain objects. It simply does that, it doesn't perform any business logic; just conversion from storage data to domain data.

You can also see how our current mixin injector would break this code: it relies on other objects in its variables scope: the DAO, plus a private method to offload the task of mapping domain property names ot DB columns.

For completeness here's the DAO, although it's not relevant to this exercise other than needing to exist:

component {

    function getRecords(required orderBy) {
        records = queryNew("id,mi,en", "int,varchar,varchar", [
            [1, "tahi", "one"],
            [2, "rua", "two"],
            [3, "toru", "three"],
            [4, "wha", "four"]
        ])

        return queryExecute("SELECT * FROM records ORDER BY #orderBy#", {}, {dbtype="query"})
    }
}

And the test to make sure our mixing-in will all work is as follows:

it("binds mixed-in methods to their original calling context when using a mixinMap", () => {
    di = new advanced.DependencyInjectionImplementor()
    model = new advanced.MyModel()
    di.wireStuffIn(
        model,
        new advanced.MyRepository(new advanced.MyDao()),
        {
            getObjectsFromStorage : {}
        }
    )

    result = model.getObjects(orderBy="maori")

    expect(result).toBeArray()
    expect(result).toHaveLength(4)
    expect(result).toBe([
        new Number("rua"),
        new Number("tahi"),
        new Number("toru"),
        new Number("wha")
    ])
})

When I run the test, you see what I mean about all that context malarcky:


We've injected getObjectsFromStorage, but not the rest of the stuff it needs to work, and they're not magically there in the next context it's being run from. Solving this problem is super easy. Currently we're injecting the method like this (from DependencyInjectionImplementor.cfc):

mixinMap.each((sourceMethod, mapping) => {
    targetMethod = mapping.keyExists("target") ? mapping.target : sourceMethod
    requestedAccess = mapping.keyExists("access") ? mapping.access : "private"

    targetAccess = requestedAccess == "public" ? "public" : "private"

    scopes[targetAccess][targetMethod] = someMixin[sourceMethod]
})

We just need to change it to this:

mixinMap.each((sourceMethod, mapping) => {
    targetMethod = mapping.keyExists("target") ? mapping.target : sourceMethod
    requestedAccess = mapping.keyExists("access") ? mapping.access : "private"

    targetAccess = requestedAccess == "public" ? "public" : "private"

    proxy = () => someMixin[sourceMethod](argumentCollection=arguments)

    scopes[targetAccess][targetMethod] = proxy
})

We wrap the call to the original method in a proxy function, and inject the proxy. This means the method is being called in the context of its original object!

Now the test passes.

It binds mixed-in methods to their original calling context by default

It stands to reason that this is the default behaviour to use, so we'll cross-implement that back into the rest of the injection code, even if we're not using the mixinMap:

it("binds mixed-in methods to their original calling context by default", () => {
    di = new advanced.DependencyInjectionImplementor()
    model = new advanced.MyModel()
    di.wireStuffIn(
        model,
        new advanced.MyRepository(new advanced.MyDao())
    )

    result = model.getObjects(orderBy="english")

    expect(result).toBeArray()
    expect(result).toHaveLength(4)
    expect(result).toBe([
        new Number("wha"),
        new Number("tahi"),
        new Number("toru"),
        new Number("rua")
    ])
})

And the implementation:

private function mixinUsingMixin(someObject, someMixin) {
    someObject.__putVariable = putIntoVariables
    structKeyArray(someMixin).each((methodName) => {
    
        proxy = () => someMixin[methodName](argumentCollection=arguments)

        someObject.__putVariable(someMixin[methodName]proxy, methodName)
    })
    structDelete(someObject, "__putVariable")
}

It doesn't bind mixed-in function to their original calling context if bind option is false

One last thing. Maybe one might want to still use the old-school approach without the proxy for some reason. In this super contrived example we actually want the mixed-in method to bind to the target location's context. Here's a model:

component {

    function init() {
        variables.status = true
    }
}

And the method we want to mix-in:

component {

    function getStatus() {
        return variables.status
    }
}

It's just providing a way to return the status of the target object to calling code. Daft example, but you get the idea. here's the test:

it("doesn't bind mixed-in function to their original calling context if bind option is false", () => {
    di = new advanced.DependencyInjectionImplementor()
    model = new advanced.MyModel()
    di.wireStuffIn(
        model,
        new advanced.MyStatusLib(),
        {getStatus = {access="public", bind=false}}
    )

    result = model.getStatus()

    expect(result).toBeTrue()
})

And the implementation:

private function mixinUsingMap(someObject, someMixin, mixinMap) {
    someObject.__getVariables = getVariables
    scopes = {
        public = someObject,
        private = someObject.__getVariables()
    }
    structDelete(someObject, "__getVariables")

    mixinMap.each((sourceMethod, mapping) => {
        targetMethod = mapping.keyExists("target") ? mapping.target : sourceMethod
        requestedAccess = mapping.keyExists("access") ? mapping.access : "private"

        targetAccess = requestedAccess == "public" ? "public" : "private"
        
        bind = mapping.keyExists("bind") && isBoolean(mapping.bind) ? mapping.bind : true

        proxy = bind
            ? () => someMixin[sourceMethod](argumentCollection=arguments)
            : someMixin[sourceMethod]

        scopes[targetAccess][targetMethod] = proxy
    })
}

(Note that this code does not work on ColdFusion, but works fine on Lucee. I have not worked out what's up with it yet, and will report back once I've looked at it. ColdFusion's parser is getting confused and it gives a compile error. The code is syntactically correct though).

That deals with it, and that's it for this exercise. As I said before don't actually do this. It'll make your code impenetrable to follow, with everyone wondering how you're calling methods that aren't defined in the class yer looking at. It's a shit way of going about things. I'm thinking of a third part to this when the mixing-in is at runtime still, but it's handled in a more explicit fashion in the target class, so the code stays clear. I've not worked out how to do this yet, but I have some ideas and some embryonic code somewhere.

TDD is your friend

I've been adding these test cases iteratively, and making sure all the previous test cases still pass with every update I make to the implementation. This directs how I design the code, and makes sure I take small steps. This is gold. And you are - IMO - a fool if it's not how you work on a daily basis.

 

Righto.

--
Adam

PS: I just noticed I missed a test case in all this. It's related to the last test case I created. Can you spot what I've missed? Hint: it's not some "nice to have" sort of test case; I've actually written some code that I'm not testing, and was not actually related to the case I was trying to fulfil.

Sunday 6 June 2021

CFML: messing around with mixins (part 1)

G'day:

There was a conversation on the CFML Slack channel the other day about mixing-in functions into objects in CFML. This stemmed from some of the way CFWheels has been architected, such as how the main Controller class is composed:

component output="false" displayName="Controller" {

    include "controller/functions.cfm";
    include "global/functions.cfm";
    include "view/functions.cfm";
    include "plugins/standalone/injection.cfm";
    if (
        IsDefined("application") && StructKeyExists(application, "wheels") && StructKeyExists(
            application.wheels,
            "viewPath"
        )
    ) {
        include "../#application.wheels.viewPath#/helpers.cfm";
    }

}

I hasten to add that I think this is a questionable design approach - and one I would never personally use or advocate. The only library of functions that belongs in this class is the controller/functions.cfm ones; clearly the ones in the vaguely-named global/functions.cfm don't belong in a class called Controller, and it's even worse that view functions are being injected into a controller class. I can't even speak to what functionality is in global/functions.cfm, but I suspect it's just a bunch of stuff that was left over once everything else found its way in a properly named/designed library. But anyhow, it should be its own class, and injected into the Controller object compositionally, via dependency injection or something. Similarly a controller should not be polluted with view methods, but if a controller needs to call methods on a view object - entirely reasonable - then that view object should also be a dependency of the controller. Ugh.

But anyway. CFWheels is where it is, and this is how it does things, and hence the topic of mixins.

To back up a step, mixins are a strategy of code reuse; basically if used cautiously they can be used to effect a poor-person's implementation of multiple inheritance in languages that don't support it, or a kind of shonky half-baked implementation of the dependency inversion principle. You can probably tell I don't like the idea. I much prefer the stategy of eschewing inheritance (multiple, single or otherwise) wherever possible in favour of a composition strategy - as implemented by dependency injection.

Some languages have formal language constructs for effecting "mixing in". I've seen traits being used in PHP before. Here's a quick example:

trait MyExcellentLib {
  function doExcellentThing() {
    echo "excellent";
  }
}

trait MyCoolLib {
  function applyCoolness() {
    echo "cool";
  }
}

class MyModel {
  use MyExcellentLib;
  use MyCoolLib;

  function executeSomeStuff() {
    $this->doExcellentThing();
    $this->applyCoolness();
  }
}

$model = new MyModel();

$model->executeSomeStuff();

And Ruby does similar with modules. Here's an analogous example of the PHP one in Ruby:

module MyExcellentLib
  def doExcellentThing
    puts "excellent"
  end
end

module MyCoolLib
  def applyCoolness
    puts "cool"
  end
end

class MyModel
  include MyExcellentLib
  include MyCoolLib

  def executeSomeStuff
    doExcellentThing
    applyCoolness
  end
end

model = MyModel.new

model.executeSomeStuff

And CFML's version would be much the same as Ruby's; including .cfm files, as per the CFWheels example above. The problem is one can only include CFML script files (I don't mean "CFScript", I mean scripts (.cfm) as opposed to components (.cfc)). One cannot go include path.to.MyComponent or include "/path/to/MyComponent.cfc". And one certainly cannot include an object. To mixin an object one would need to do it at runtime, not compile time.

This got me thinking, and I decided to write some experimental code to see what I could do with this. If anything. And as-always, I'm TDDIng the exercise.

Compile-time implementation of mix-ins: it mixes-in the required functions

As a baseline, I'm just gonna test the include approach to doing this. Here's the test:


import cfmlInDocker.miscellaneous.mixins.compileTime.MyModel;

component extends=testbox.system.BaseSpec {

    function run() {
        describe("Testing mixin proofs of concept", () => {
            describe("Tests compile-time implementation of mix-ins", () => {
                it("mixes-in the required functions", () => {
                    model = new MyModel()
                    result = model.executeSomeStuff()

                    expect(result).toBe("ExcellentCool")
                })
            })
        })
    }
}

And the code that fulfils the test:

// MyModel.cfc

component {
    include "./myCoolLib.cfm";
    include "./myExcellentLib.cfm";

    function executeSomeStuff() {
        return doExcellentThing() & applyCoolness()
    }
}
<cfscript> // myCoolLib.cfm
    function applyCoolness() {
        return "Cool"
    }
</cfscript>
<cfscript>// myExcellentLib.cfm
    function doExcellentThing() {
        return "Excellent"
    }
</cfscript>

(In all cases here, the test passes with the given code, so I'll spare you saying "and that works" each time)

Baseline done. What I want to do now is to do the equivalent with an object, not an include. And at runtime.

Run-time simple implementation of mix-ins: it mixes-in the required functions

it("mixes-in the required functions", () => {
    di = new simple.DependencyInjectionImplementor()
    model = new simple.MyModel()
    di.wireStuffIn(model, new simple.MyExcellentLib())
    di.wireStuffIn(model, new simple.MyCoolLib())

    result = model.executeSomeStuff()

    expect(result).toBe("ExcellentCool")
})

Now I have a DependencyInjectionImplementor class that handles all the wiring-up of things:

// DependencyInjectionImplementor.cfc
component  {

    function wireStuffIn(someObject, someMixin) {
        someObject.__putVariable = putIntoVariables
        structKeyArray(someMixin).each((methodName) => {
            someObject.__putVariable(someMixin[methodName], methodName)
        })
        structDelete(someObject, "__putVariable")
    }

    function putIntoVariables(value, key){
        variables[key] = value
    }
}
// MyModel.cfc
component {
    function executeSomeStuff() {
        return doExcellentThing() & applyCoolness()
    }
}
// MyExcellentLib.cfc
component {
    function doExcellentThing() {
        return "Excellent"
    }
}
// MyCoolLib.cfc
component {
    function applyCoolness() {
        return "Cool"
    }
}

It just takes and object, loops over its exposed methods, and pops them into the variables scope of the target object.

All those source code files are in /miscellaneous/mixins/runtime/simple.

Job done, but it's a bit basic. Let's keep going…

It mixes-in a subset of functions from a lib

What say one only wants to mixin a subset of the methods from the object? Pretty straight forward:

it("mixes-in a subset of functions from a lib", () => {
    di = new advanced.DependencyInjectionImplementor()
    model = new advanced.MyModel()
    di.wireStuffIn(model, new advanced.MyBrilliantLib(), ["radiateBrilliance"])

    result = model.executeSomethingBrilliant()
    expect(result).toBe("brilliance")

    expect(() => model.failAtDoingItBrilliantly()).toThrow(type="expression")
})
// DependencyInjectionImplementor.cfc
component  {

    function wireStuffIn(someObject, someMixin, mixinMap) {
        someObject.__putVariable = putIntoVariables

        functionsToMixin = arguments.keyExists("mixinMap")
            ? mixinMap
            : structKeyArray(someMixin)

        functionsToMixin.each((methodName) => {
            someObject.__putVariable(someMixin[methodName], methodName)
        })

        structDelete(someObject, "__putVariable")
    }

    function putIntoVariables(value, key){
        variables[key] = value
    }
}
// MyModel.cfc
component {

    // ...

    function executeSomethingBrilliant() {
        return radiateBrilliance()
    }

    function failAtDoingItBrilliantly() {
        return doItBrilliantly()
    }
}
// MyBrilliantLib.cfc
component {
    function radiateBrilliance() {
        return "brilliance"
    }
    function doItBrilliantly() {
        return "done brilliantly"
    }
}

(That source code is at /src/miscellaneous/mixins/runtime/advanced).

We can pass in an array of method names, and loop over that if so. Otherwise just continue to loop over the whole object as per before.

Note that because I've not mixed-in doItBrilliantly, we should expect it to error if we then call it.

It should go without saying that any earlier tests I have written continue to pass as I build new functionality into this. Such is the benefit of TDDing… I have that safety net.

It remaps function names if the map specifies it

The next task I set myself is to allow the mixinMap to specify a new name for the mixed-in method, should for some reason one want to do that (avoid naming collisions or something).

it("remaps function names if the map specifies it", () => {
    di = new advanced.DependencyInjectionImplementor()
    model = new advanced.MyModel()
    di.wireStuffIn(
        model,
        new advanced.MyBrilliantLib(),
        {
            "radiateBrilliance" = {target="shine"},
            "doItBrilliantly" = {}
        }
    )

    result = model.performBrilliantThings()
    expect(result).toBe("brilliance done brilliantly")
})
// DependencyInjectionImplementor.cfc
component  {

    function wireStuffIn(someObject, someMixin, mixinMap) {
        someObject.__putVariable = putIntoVariables

        arguments.keyExists("mixinMap")
            ? mixinUsingMap(someObject, someMixin, mixinMap)
            : mixinUsingMixin(someObject, someMixin)

        structDelete(someObject, "__putVariable")
    }

    private function mixinUsingMixin(someObject, someMixin) {
        structKeyArray(someMixin).each((methodName) => {
            someObject.__putVariable(someMixin[methodName], methodName)
        })
    }

    private function mixinUsingMap(someObject, someMixin, mixinMap) {
        mixinMap.each((sourceMethod, mapping) => {
            targetMethod = mapping.keyExists("target") ? mapping.target : sourceMethod
            someObject.__putVariable(someMixin[sourceMethod], targetMethod)
        })
    }

    function putIntoVariables(value, key){
        variables[key] = value
    }
}
// MyModel.cfc
component {

    // ...

    function performBrilliantThings() {
        return shine() & " " & doItBrilliantly()
    }
}

This looks a bit more complicated, but I've just separated-out the two mixing-in methods into their own functions now, to make things more clear.

It handles public/private method access on the mixed-in function

This is more interesting. Now I'm allowing the map to specify whether to mix-in the method as being private or public in the target. Previously they were always private.

it("handles public/private method access on the mixed-in function", () => {
    di = new advanced.DependencyInjectionImplementor()
    model = new advanced.MyModel()
    di.wireStuffIn(
        model,
        new advanced.MyBestLib(),
        {
            improveGoodness = {target="makeItBetter"},
            makeBest = {access="public"}
        }
    )

    result = model.executeSomeOtherStuff()
    expect(result).toBe("better best")

    expect(() => model.makeItBetter()).toThrow(type="expression")
})
// DependencyInjectionImplementor.cfc
component  {

    function wireStuffIn(someObject, someMixin, mixinMap) {
        arguments.keyExists("mixinMap")
            ? mixinUsingMap(someObject, someMixin, mixinMap)
            : mixinUsingMixin(someObject, someMixin)
    }

    private function mixinUsingMixin(someObject, someMixin) {
        someObject.__putVariable = putIntoVariables
        structKeyArray(someMixin).each((methodName) => {
            someObject.__putVariable(someMixin[methodName], methodName)
        })
        structDelete(someObject, "__putVariable")
    }

    private function mixinUsingMap(someObject, someMixin, mixinMap) {
        someObject.__getVariables = getVariables
        scopes = {
            public = someObject,
            private = someObject.__getVariables()
        }
        structDelete(someObject, "__getVariables")

        mixinMap.each((sourceMethod, mapping) => {
            targetMethod = mapping.keyExists("target") ? mapping.target : sourceMethod
            targetAccess = mapping.keyExists("access") ? mapping.access : "private"

            scopes[targetAccess][targetMethod] = someMixin[sourceMethod]
        })
    }

    function getVariables(){
        return variables
    }

    function putIntoVariables(value, key){
        variables[key] = value
    }
}
// MyModel.cfc
component {

    // ...

    function executeSomeOtherStuff(){
        return variables.makeItBetter() & " " & this.makeBest()
    }
}
// MyBestLib.cfc
component {

    function improveGoodness() {
        return "better"
    }

    function makeBest() {
        return "best"
    }
}

It ignores access modifiers other than public / private (falling back to private)

I introduced a small bug into that last implementation. I could specify any access level I wanted, whereas the only valid access levels here are public or private. I need to fix this.

it("ignores access modifiers other than public / private (falling back to private)" , () => {
    di = new advanced.DependencyInjectionImplementor()
    model = new advanced.MyModel()
    di.wireStuffIn(
        model,
        new advanced.MyBestLib(),
        {
            makeBest = {access="INVALID"}
        }
    )
    result = model.checkIfItsTheBest()
    expect(result).toBe("best")

    expect(() => model.makeBest()).toThrow(type="expression")
})
private function mixinUsingMap(someObject, someMixin, mixinMap) {
    // ...

    mixinMap.each((sourceMethod, mapping) => {
        // ...
        requestedAccess = mapping.keyExists("access") ? mapping.access : "private"

        targetAccess = requestedAccess == "public" ? "public" : "private"

        scopes[targetAccess][targetMethod] = someMixin[sourceMethod]
    })
}

I could have rolled that into one line, but it was getting a bit awkward-looking, so I figured two lines - each with clear labels - were clearer in intent.

Oh! And the model file:

component {

    // ...

    function checkIfItsTheBest(){
        return variables.makeBest()
    }
}

Back up in the test I just make sure that it's only available in the private scope.

End of round 1

That's where I got to yesterday when writing the code for all this. I was discussing this with Tom King (lead contributor on CFWheels), and he observed one flaw in the way I'm doing these mixins is that they lose the context of the object they were originally homed in. Because of how CFML handles injecting methods by reference like how I am doing here, once the method is in the target object, any references that method makes to variables or this is a reference to the target object, not the source object that they came from. My initial reaction to this was "well: yes", but I'm only thinking of mixing functions from function libraries here. There's also a usecase for a mix-in library to also require its own context, so I need to address this too. I worked out how to do this pretty quickly (which made me pleased with myself, I have to admit), but I figured I need to verify the behaviour some more before I am happy with it, plus also I'm now thinking that how these contexts are bound should be perhaps optional. And I already have a lot of content in this article already, so I'll work on the next wodge of ideas some more before writing them up. For now I'm gonna call it quits, have a beer, and play dumb-arse games for the balance of the evening. Ooh: and eat! It's eating time.

(I've just written Part 2 of this exercise).

Righto.

--
Adam

Sunday 2 May 2021

CFML: pseudo-constructor polymorphic inheritance expectations management

G'day:

Well there's a sequence of words I never expected to write down.

I just ran across something that I was reasonably surprised by when I first saw it. But having looked at it some more, I'm not sure. So I thought I'd ask the 3-4 people who actually read this blog to offer their insight.

Consider this traditional example of polymorphism in play:

// Base.cfc
component {

    function runMe(){
        doThings()
    }

    function doThings(){
        writeOutput("BaseApp doThings called")
    }
}

// Sub.cfc
component extends=Base {

    public function doThings(){
        super.doThings()
        writeOutput("SubApp doThings called")
    }
}

// from StandardInheritanceTest.cfc
it("A subclass will override a base class method", () => {
    o = new Sub()
    o.runMe()

    expect(o.stack).toBe([
        "Base doThings called",
        "Sub doThings called"
    ])
})

The test (which passes) confirms what we'd expect: when runMe calls doThings, Because it's being called on a Sub object, the reference to doThings is referring to Sub.doThings even though the call is in Base. Hopefully no surprises there.

But what about this example:

// Base.cfc
component {

    this.stack = []

    doThings()

    function doThings(){
        this.stack.append("Base doThings called")
    }
}

// Sub.cfc
component extends=Base {

    function doThings(){
        super.doThings()
        this.stack.append("Sub doThings called")
    }
}

// from PseudoConstructorInheritanceTest.cfc
it("A subclass will override a base class method", () => {
    o = new Sub()

    expect(o.stack).toBe([
        "Base doThings called",
        "Sub doThings called"
    ])
})

The difference here is the call to doThings is not done by the test, it's done within the pseudo-constructor of the Base component.

And in this case the test fails:

Expected [[Base doThings called, Sub doThings called]] but received [[Base doThings called]]

It would seem the pseudo-constructor code of a base-class is not aware it's being called from a sub-class. This doesn't seem right to me?

I'm running this code on Lucee, but I ran equivalent code on ColdFusion and the results were the same (so I guess that's something). And given the behaviour is the same on both I'm thinking this is more me not understanding something, rather than a bug. What do you think?

BTW whilst testing this I found out I can get the behaviour I actually want with a slight tweak to Sub.cfc. I changed from this:

component extends=Base {

    function doThings(){
        super.doThings()
        this.stack.append("Sub doThings called")
    }
}

To this:

component extends=Base {

    doThings()

    function doThings(){
        this.stack.append"Sub doThings called")
    }
}

IE: I replicate the way doThings is called in Base.cfc: from the pseudo-constructor. Both calls are made. For my purposes this will work fine. But I still do find it curious.

Or am I being daft?

Righto.

--
Adam

Tuesday 20 April 2021

Why I've been looking at CFML again recently

G'day:

Just a quick one. You might have noticed that the subject matter of this thing abruptly changed from the usual PHP-oriented stuff, back to looking at CFML-oriented stuff. This deserves an explanation.

In case you didn't know, I finished my role at Hostelworld.com about a year ago. Since then I've been largely pottering around, avoiding CV-19 and joining in with the ubiquitous societal disallusionment / confusion / malaise surrounding the realities of not being able to do whatever I want whenever I want to, and being largely locked-up most of the time.

For reasons I won't go into, I've not needed to look for work. So I decided to wait until "things settled down". Which basically wrote-off 2020. I am really lucky to be in that position, and I feel for people who had to take things more urgently in the commercial climate that was 2020.

2021 rolled around and I decided I had better get my head out of my arse and find a job. This was quite daunting as I'd not needed to interview for a job for ten years, and I was reasonably rusty at it. I also decided to be fairly picky about what I looked at, given there was no financial desperation in the mix.

I went for two job interviews, and didn't get either. One made me laugh cos they reckoned I didn't know enough about testing. It's probably for the best I didn't end up there.

A week or so ago I started to talk to Easy Direct Debits Ltd, and this has worked out well for me (and hopefully them…). I'm starting today - I'll clock-on in about 15min - as "Technical Team Lead". Cool. I've met (well: via Zoom) two of the bods above me in the foodchain there, and they both seem like good blokes. This makes for a positive start. I generally look forward to going to work each day, but I'm enthusiastic (as much as I get enthusiastic about stuff) about cracking on in an environment that's new to me.

But not that new, and back to the subject line of this article: it's a CFML shop. I'm a wee bit rusty with my CFML, hence giving myself some exercises this last week. And my boss has given me more to do today. Ha. I will also be maintaining my focus on TDD, automated testing, and code quality. This is a big part of my role there. And this is excellent.

I'll be rejoining the CFML Slack community shortly. Apologies in advance to everyone there ;-)

And just to close… I can't not link to this, in the circumstances:

Righto.

--
Adam

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 29 April 2017

CFML: Lucee 13 v 414 v 6 ColdFusion. We have a "winner"

G'day:
Argh. CFML again. I'm doing this because I was a bit of a meanie to Brad on the CFML Slack Channel, and promised I'd try to play nice with one of our current exercises set out by Ryan:

Todays challenge: post something you can do in CFML that you don’t think the majority of people know. Maybe its a little-known function, or some java integration, or a technique from another language that isn’t common in CFML - or even just something that you’ve learned from this slack group - if you didn’t know it before someone else probably doesn’t know it now. Doesn’t even have to be a good idea or something you’ve ever actually used but bonus points for useful stuff. Can apply to ACF [ed: Adobe ColdFusion, or could just  be ColdFusion or CF. Dunno why ppl say "ACF"] or Lucee but bonus points for something universal.
This is a great idea. And whilst I am trying to not use CFML any more due to it not being a good use of my time, I still try to help other people with it on the Slack channel, and CFML does do some good stuff.

I couldn't think of anything interesting that no-one else would know about, but I figured I'd show people a different approach to something. Just as food for thought.

In CFML, when hitting the DB one receives the data back not as an array of objects or an array of structs, but as a single "Query Object", which internally contains the data, and exposes access to said data by various functions, methods, and statements. This is fine which restricted to CFML code, but increasingly code these days needs to interchange with other systems, and sometimes it's a bit of a pain converting from a Query object to something more interchangeable like... an array of objects (or structs in CFML). There's no native method to do this, but it's easy enough to do with a reduction:

// fake a DB call
numbers = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"one","tahi"],
    [2,"two","rua"],
    [3,"three","toru"],
    [4,"four","wha"]
]);

numbersAsArray = numbers.reduce(function(elements, row){
    return elements.append(row);
}, []);

writeDump({
    numbers=numbers,
    numbersAsArray=numbersAsArray
});

(run this yerself on trycf.com)

Here I'm faking the DB hit, but that can be chained together too:

numbersAsArray = queryExecute("SELECT * FROM numbers")
    .reduce(function(rows, row){
        return rows.append(row);
    }, []);

All good, and nice and compact. Not very inspiring though.

So as a next step I decided to add some more method calls in there. Let's say I only wanted numbers greater than 5, I wanted the keys in the structs to be different from the ones in the DB, and I wanted it sorted in reverse order. Obviously this is all easily done in the DB:

numbers = queryExecute("
    SELECT id AS value, en AS english, mi AS maori
    FROM numbers
    WHERE id > 5
    ORDER BY id DESC
");

But sometimes we have to play the hand we've been dealt, and we cannot change the recordset we're getting back.

Of course we could also do this with a query-of-query too, but CFML's internal SQL implementation offers... "challenges" of its own, so let's forget about that.

Anyway, I ended up with this:

numbersAsArray = queryExecute("SELECT * FROM numbers")
    .filter(function(row){
        return row.id > 5;
    })
    .map(function(row){
        return {value=row.id, english=row.en, maori=row.mi};
    }, queryNew("value,english,maori"))
    .reduce(function(rows=[], row){
        return rows.append(row);
    })
    .sort(function(e1,e2){
        return e2.value - e1.value;
    })
;

That's all straight forward. We retain only the rows we want with filter, we change the column names with map, and again convert the result to be an array of structs with reduce, then finally we re-order them with sort.

That's cool. And the result is...

YES

Huh? I mean literally... the value of numbersAsArray was "YES". Groan. For the uninitiated, the string "YES" is a boolean truthy value in CFML (CFML also has true and false, but it favours "YES" and "NO" for some common-sense-defying reason). And indeed some old-school CFML functions which should have been void functions instead return "YES". But the method versions should not: methods - where sensible - should return a result so they can be chained to the next method call. Like I'm trying to do here: the end of the chain should be the answer.

I could see how this would continue, so I decided to start keeping score of the bugs I found whilst undertaking this exercise.

ColdFusion: 1.

I pared my code back to get rid of any irrelevant bits for the sake of a SSCCE, and ran it on Lucee for comparison. I just got an error, but not one I expected.

I have an Application.cfc set up her to define my datasource, and I had this:

component {
    this.name = getCurrentTemplatePath().hash();
    this.datasource = "scratch_mysql";
}

Now I didn't need the DSN for this SSCCE, but the Application.cfc was still running obviously. And it seems Lucee does not implement the hash method:

Lucee 5.1.3.18 Error (expression)
MessageNo matching Method/Function for String.hash() found

Lucee joins the scoring:

ColdFusion 1 - 1 Lucee

I use the hash function instead of the method to name my application, and at least now Lucee gets to the code I want to run.

numbers = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"one","tahi"],
    [2,"two","rua"],
    [3,"three","toru"],
    [4,"four","wha"]
]);

reversed = numbers.sort(function(n1,n2){
    return n2.id - n1.id;
});

writeDump([reversed,numbers]);

And the result:

Array
1
Query
Execution Time: 0 ms
Record Count: 4
Cached: No
Lazy: No 
idenmi
14fourwha
23threetoru
32tworua
41onetahi
2
Query
Execution Time: 0 ms
Record Count: 4
Cached: No
Lazy: No 
idenmi
14fourwha
23threetoru
32tworua
41onetahi

Hang on. Lucee's changed the initial query as well. If it's returning the result, then it should not also be changing the initial value. But I'm gonna say this is due to a sort of sideways compatibility with ColdFusion:

array
1YES
2
query
enidmi
1four4wha
2three3toru
3two2rua
4one1tahi

As it doesn't return the value from the method, it makes sense to act on the initial value itself.

But if Lucee's gonna copy ColdFusion (which it should be) then it should be copying it properly.

ColdFusion 1 - 2 Lucee

To mitigate this, I decide to duplicate the initial query first:

reversed = numbers.duplicate();
reversed.sort(function(n1,n2){
    return n2.id - n1.id;
});

This works fine on ColdFusion:

array
1
query
ENIDMI
1four4wha
2three3toru
3two2rua
4one1tahi
2
query
enidmi
1one1tahi
2two2rua
3three3toru
4four4wha

But breaks on Lucee:

 Error:
No matching Method/Function for Query.duplicate() found on line 9

Hmmm. Well I s'pose the duplicate method doesn't seem to be documented, but it was added in CF2016. This is getting in my way, so I'm still chalking it up to an incompat in Lucee:

ColdFusion 1 - 3 Lucee

(I probably should add a documentation bug with ColdFusion too, but that's a separate matter).

Anyway, that's mostly an aside. In my example what I am sorting is an intermediary value anyhow, so it doesn't matter that it gets sorted as well as being returned. For my purposes I am not using ColdFusion any more, just Lucee, as I'm specifically showing the method chaining thing, and we already know ColdFusion messes this up with how sort works.

So here we go, all done:

numbers = queryExecute("SELECT * FROM numbers")
    .map(function(row){
        return {value=row.id, english=row.en, maori=row.mi};
    }, queryNew("value,english,maori"))
    .filter(function(row){
        return row.value > 5;
    })
    .sort(function(e1,e2){
        return e2.value - e1.value;
    })
    .reduce(function(rows, row){
        return rows.append(row);
    }, [])
;

writeDump(numbers);

And the output:

Array
1
Struct
en
Empty:null
english
stringten
id
Empty:null
maori
stringtekau
mi
Empty:null
value
number10
2
Struct
en
Empty:null
english
stringnine
id
Empty:null
maori
stringiwa
mi
Empty:null
value
number9
3
Struct
en
Empty:null
english
stringeight
id
Empty:null
maori
stringwaru
mi
Empty:null
value
number8
4
Struct
en
Empty:null
english
stringseven
id
Empty:null
maori
stringwhitu
mi
Empty:null
value
number7
5
Struct
en
Empty:null
english
stringsix
id
Empty:null
maori
stringono
mi
Empty:null
value
number6

OK, now WTF is going on? Lucee hasn't remapped the columns properly. it's added the new ones, but it's also included the old ones. It ain't supposed to do that. Contrast ColdFusion & Lucee with some more simple code:

numbers = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"one","tahi"],
    [2,"two","rua"],
    [3,"three","toru"],
    [4,"four","wha"]
]);
remapTemplate = queryNew("value,english,maori"); 

reMapped = numbers.map(function(row){
    return {value=row.id, english=row.en, maori=row.mi};
}, remapTemplate);

writeDump(reMapped);

ColdFusion:

query
ENGLISHMAORIVALUE
1onetahi1
2tworua2
3threetoru3
4fourwha4

Lucee:

Query
Execution Time: 0 ms
Record Count: 4
Cached: No
Lazy: No 
valueenglishmaoriidenmi
11onetahi
Empty:null
Empty:null
Empty:null
22tworua
Empty:null
Empty:null
Empty:null
33threetoru
Empty:null
Empty:null
Empty:null
44fourwha
Empty:null
Empty:null
Empty:null

Sigh. What's supposed to be returned by a map operation on a query is a new query with only the columns from that remapTemplate query. That's what it's for.

ColdFusion 1 - 4 Lucee

On a whim I decided to check what Lucee did to the remapTemplate:

Query
Execution Time: 0 ms
Record Count: 4
Cached: No
Lazy: No 
valueenglishmaoriidenmi
11onetahi
Empty:null
Empty:null
Empty:null
22tworua
Empty:null
Empty:null
Empty:null
33threetoru
Empty:null
Empty:null
Empty:null
44fourwha
Empty:null
Empty:null
Empty:null

ColdFusion 1 - 5 Lucee

This situation is slightly contrived as I don't care about that query anyhow. But what if I was using an extant query which had important data in it?

remapTemplate = queryNew("value,english,maori", "integer,varchar,varchar", [
    [5, "five", "rima"]
]);

So here I have a query with some data in it, and for whatever reason I want to remap the numbers query to have the same columns as this one. But obviously I don't want it otherwise messed with. Lucee mungs it though:

Query
Execution Time: 0 ms
Record Count: 5
Cached: No
Lazy: No 
valueenglishmaoriidenmi
15fiverima
21onetahi
Empty:null
Empty:null
Empty:null
32tworua
Empty:null
Empty:null
Empty:null
43threetoru
Empty:null
Empty:null
Empty:null
54fourwha
Empty:null
Empty:null
Empty:null

Not cool.

But wait. We're not done yet. Let's go back to some of my original code I was only running on ColdFusion:

numbersAsArray = queryExecute("SELECT id,mi FROM numbers LIMIT 4")
    .reduce(function(rows=[], row){
        return rows.append(row);
    })
;
writeDump(numbersAsArray);

This was part of the first example, I've just ditched the filter, map and sort: focusing on the reduce. On ColdFusion I get what I'd expect:

array
1
struct
ID1
MItahi
2
struct
ID2
MIrua
3
struct
ID3
MItoru
4
struct
ID4
MIwha

On Lucee I get this:

Lucee 5.1.3.18 Error (expression)
Messagecan't call method [append] on object, object is null
StacktraceThe Error Occurred in
queryReduceSimple.cfm: line 4 
2: numbersAsArray = queryExecute("SELECT id,mi FROM numbers LIMIT 4")
3: .reduce(function(rows=[], row){
4: return rows.append(row);
5: })
6: ;

Hmmm. What's wrong now? Oh. It's this:

reduce(function(rows=[], row)

Notice how I am giving a default value to the first argument there. This doesn't work in Lucee.

ColdFusion 1 - 6 Lucee

This is easy to work around, because reduce functions take an optional last argument which is the initial value for that first argument to the callback, so I can just re-adjust the code like this:

.reduce(function(rows , row){
    return rows.append(row);
}, [])

OK, at this point I give up. Neither implementation of CFML here - either ColdFusion's or Lucee's - is good enough to do what I want to do. Oddly: Lucee is far worse on this occasion than ColdFusion is. That's disappointing.

So currently the score is 1-6 to Lucee. How did I get to 4-13?

I decided to write some test cases with TestBox to demonstrate what ought to be happening. And with the case of duplicate, I tested all native data-types I can think of:

  • struct
  • array
  • query
  • string
  • double (I guess "numeric" in CFML)
  • datetime
  • boolean
  • XML

Lucee failed the whole lot, and ColdFusion failed on numerics and booleans. As this is undocumented behaviour this might seem a bit harsh, but I'm not counting documentation errors against ColdFusion in this case. Also there's no way I'd actually expect numerics and booleans to have a duplicate method... except for the fact that strings do. Now this isn't a method bubbling through from java.lang.String, nor is it some Java method of ColdFusion's string implementation (they're just java.lang.Strings). This is an actively-created CFML member function. So it seems to me that - I guess for the sake of completeness - they implemented for "every" data type... I mean it doesn't make a great deal of sense on a datetime either, really, does it? So the omission of it from numerics and booleans is a bug to me.

This leaves the score:

ColdFusion 3 - 13 Lucee

The last ColdFusion point was cos despite the fact that with the sort operation it makes sense to alter the initial object if the method doesn't return the sorted one... it just doesn't make sense that the sort method has been implemented that way. It should leave the original object alone and return a new sorted object.

ColdFusion 4 - 13 Lucee

My test cases are too long to reproduce here, but you can see 'em on Github: Tests.cfc.

Right so...

Ah FFS.

... I was about to say "right, so that's that: not a great experience coming up with something cool to show to the CFMLers about CFML. Cos shit just didn't work. I found 17 bugs instead".

But I just had a thought about how sort methods work in ColdFusion, trying to find examples of where sort methods return the sorted object, rather than doing an inline support. And I I've found more bugs with both ColdFusion and Lucee.

Here are the cases:

component extends="testbox.system.BaseSpec" {
    function run() {
        describe("Other sort tests", function(){
            it("is a baseline showing using BIFs as a callback", function(){
                var testString = "AbCd";
                var applyTo = function(object, operation){
                    return operation(object);
                };

                var result = applyTo(testString, ucase);
                
                expect(result).toBeWithCase("ABCD");
            });
            describe("using arrays", function(){
                it("can use a function expression calling compareNoCase as a string comparator when sorting", function(){
                    var arrayToSort = ["d","C","b","A"];
                    
                    arrayToSort.sort(function(e1,e2){
                        return compareNoCase(e1, e2);
                    });
                    
                    expect(arrayToSort).toBe(["A","b","C","d"]);
                });
                it("can use the compareNoCase BIF as a string comparator when sorting", function(){
                    var arrayToSort = ["d","C","b","A"];
                    
                    arrayToSort.sort(compareNoCase);
                    
                    expect(arrayToSort).toBe(["A","b","C","d"]);
                });
            });
            describe("using lists", function(){
                it("can use a function expression calling compareNoCase as a string comparator when sorting", function(){
                    var listToSort = "d,C,b,A";
                    
                    var sortedList = listToSort.listSort(function(e1,e2){
                        return compareNoCase(e1, e2);
                    });
                    
                    expect(sortedList).toBe("A,b,C,d");
                    expect(listToSort).toBe("d,C,b,A");
                });
                it("can use the compareNoCase BIF as a string comparator when sorting", function(){
                    var listToSort = "d,C,b,A";
                    
                    var sortedList = listToSort.listSort(compareNoCase);
                    
                    expect(sortedList).toBe("A,b,C,d");
                    expect(listToSort).toBe("d,C,b,A");
                });
            });
        });
    }
}

What I'm doing here is using CFML built-in functions as the callbacks for a sort operation. This should work, because the sort operation needs a comparator function which works exactly like compare / compareNoCase: returns <0, 0, >0 depending on whether the first argument is "less than", "equal to" or "greater than" the second object according to the sort rules. As far as strings go, the built-in functions compare and compareNoCase do this. So they should be usable as callbacks. Since I think CF2016 built-in-functions have been first-class functions, so should be usable wherever something expects a function as an argument.

The first test demonstrates this in action. I have a very contrived situation where I have a function applyTo, which takes an object and a function to apply to it. In the test I pass-in the built-in function ucase as the operation. This test passes fine on ColdFusion; fails on Lucee.

ColdFusion 4 - 14 Lucee

So after I've demonstrated the technique should work, I try to use compareNoCase as the comparator for an array sort. it just doesn't work: it does nothing on ColdFusion, and on Lucee it still just errors (not gonna count that against Lucee, as it's the same bug as in the baseline test).

ColdFusion 5 - 14 Lucee

Next I try to use it on a listSort. This time ColdFusion errors as well. So this is a different bug than the doesn't-do-anything one for arrays.

ColdFusion 6 - 14 Lucee

Here are the results for just this latter tranche of test cases:

ColdFusion:



Lucee:



Fuck me, I've giving up.

This has been the most shit experience I've ever had trying to get CFML to do something. I don't think any of this code is edge-case stuff. Those higher-order functions are perhaps not as commonly used as they ought to be by the CFMLers out there, but I'm just... trying to use them.

So... sorry Brad & Ryan... I tried to come up with something worth showing to the mob that's useful in CFML, but I've failed. And my gut reaction to this exercise is that CFML can go fuck itself, basically.

Righto.

--
Adam