Showing posts with label Clojure. Show all posts
Showing posts with label Clojure. Show all posts

Tuesday 1 December 2020

Getting Docker, Clojure and VSCode all playing nice (well... almost...)

G'day:
I'm trying to re-motivate myself to do some tech type stuff in my spare time. I have a lot of spare time at the moment. 24h of the day. I still haven't really looked for a job having been made redundant a few months back (coughJunecough), and I've been largely wasting my time sleeping, napping (yes, two distinct things), and playing computer games. On one hand 2020 is a bit of a rubbish year, and a new work environment is likely to be a subpar experience, even if I can find work. On the other hand I can afford to not work for quite a while thanks to my redundancy pay out, but on still another hand (yes, fine, I have three hands for the purposes of this story), I really am not taking advantage of this spare time I have. That's why I had a look at Mingo's CF problem the other day ("looking at an issue Mingo had with ehcache and cachePut and cacheGet"). Just trying to do anything marginally "productive".

I've been wanting to learn a bit of Clojure for a while now. Not for any particular reason, and I'm not looking to shift to doing that over PHP, but just cos it's a completely different language from anything I've used before - which have all been C-ish "curly brace" type things - and my mate Sean has banged on about how good it is. I think at his suggestion I bought Living Clojure by Carin Meier a while back when I had some down time in NZ. I read the first few pages and then realised Auckland has a lot of pubs, so did that instead. This would have been about four(?) years ago. I ain't touched it since.

Here I am, sporadically trying to get to grips with Docker, and this seemed like a good mini-project... get Clojure up and running within a container, set up a project, integrate it with an IDE, and get to the point where I have a running (and passing) test for some sort of "G'day world" function. In the past I would install whatever language I was wanting to mess with natively on my PC, but I decided running it in a container is the way to go.

Note that this is pretty much just a record of what I needed to do to get to the point I could write the code and run the tests. I have very little experience with Docker, and hardly any experience with Clojure. What this means is that this is def a newbie account of what I did, and I really would not take it as instructions of how to do stuff. All I will say is that I needed to do a whole bunch of googling to work shit out, and I will summarise the results of the googling here. This is more an exercise for me to verify what I've done actually works, as having done it once... I'm now getting-rid and starting from scratch.

OK. I am starting with a minimal "empty" repo, living-clojure:

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ tree -aF --dirsfirst -L 1 .
.
├── .git/
├── .gitignore*
├── LICENSE*
└── README.md*

1 directory, 3 files
adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$

This just gives me a directory I can mount in the container to expose my code in there.

Now I need the container. Looking at dockerhub/clojure, the options there are just for running the app, not doing development, so ain't much help. I was hoping to be able to copy and paste something like "a container with Clojure and Leiningen in it wot you can as a shell for building yer project, testing it, developing it, and running it". Nothing to copy and paste and run, so I'm gonna have to work out what to do. Remember I'm a complete noob with Docker.

Getting the image down was easy enough:

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ docker pull clojure
Using default tag: latest
latest: Pulling from library/clojure
852e50cd189d: Already exists
ef17c1a94464: Pull complete
477589359411: Pull complete
0a40767d8190: Downloading [==================> ] 71.81MB/197.1MB
434967525bea: Download complete
c89e9081a4db: Download complete
b7cd84bcb910: Download complete
214999e8c5eb: Download complete
0bdd8adb02ca: Download complete

(Just showing it mid-pull there)

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
php rc-cli 657d0925a963 6 days ago 407MB
clojure latest 846a444b258f 6 days ago 586MB
[etc]

Just to start with, I just need a container based on the Clojure image, but doing nothing else. I just need to run a shell with it so I can create my Clojure project. I came up with this:

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ docker create --name living-clojure --mount type=bind,source=/mnt/c/src/living-clojure,target=/usr/src/living-clojure --workdir /usr/src/living-clojure --interactive --tty clojure /bin/bash
a1be067ee435adb6851ac06b53bb97545f38dabced73a19255f1e273b0337d1e
adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$
  • I'm mounting my host's project directory within the container;
  • and setting that to be my working dir in there too;
  • I don't completely get these two, but I wanna be able to type shit into my shell that I'm running, and this seems to make it happen;
  • and it's bash I want to interact with.

And I start this:

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ docker start --interactive living-clojure
root@a1be067ee435:/usr/src/living-clojure#

Hurrah! This seems promising: I'm in a different shell, and I'm in the working directory I specified when doing the docker create before.

Let's see if I can create a new Clojure project (which I'm lifting from the the Leiningen docs):

root@a1be067ee435:/usr/src/living-clojure# cd ..
root@a1be067ee435:/usr/src# lein new app living-clojure --force
Generating a project called living-clojure based on the 'app' template.
root@a1be067ee435:/usr/src#

I need to go up a directory level and to use --force because I already have the project directory in place, and lein new assumes one doesn't.

And this all seems to work fine (well… in that it's done "stuff"):

root@a1be067ee435:/usr/src# tree -F -a --dirsfirst -L 1 living-clojure/
living-clojure/
├── .git/
├── doc/
├── resources/
├── src/
├── test/
├── .gitignore*
├── .hgignore*
├── CHANGELOG.md*
├── LICENSE*
├── README.md*
└── project.clj*

5 directories, 6 files
root@a1be067ee435:/usr/src#

git status shows something interesting though:

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .gitignore
modified: LICENSE
modified: README.md

Untracked files:
(use "git add <file>..." to include in what will be committed)
CHANGELOG.md
doc/
project.clj
src/
test/

no changes added to commit (use "git add" and/or "git commit -a")
adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$

Note how .gitignore, LICENSE and README.md have all changed. Such is the price of doing that --force I guess. I'm gonna revert the LICENSE and README.md, but leave the .gitignore; I presume the Leiningen bods have a better idea of what git should ignore in the context of a Clojure project than GitHub's boilerplate one does.

Next… what's the code in the default project actually do?

(ns living-clojure.core
(:gen-class))

(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))

That will do for a start. Let's see if it runs:

root@a1be067ee435:/usr/src# cd living-clojure/
root@a1be067ee435:/usr/src/living-clojure# lein run
Hello, World!
root@a1be067ee435:/usr/src/living-clojure#

Cool! Is it bad I'm so pleased with a computer saying "Hello, World!"? Ha.

I'm being a bit naughty because I'm running the code before I've tested it. But I figure... not my code, so it's OK. Anyway, there is a test, although it does not test the code in the app. Sigh. But anyhow:

root@a1be067ee435:/usr/src/living-clojure# cat test/living_clojure/core_test.clj
(ns living-clojure.core-test
(:require [clojure.test :refer :all]
[living-clojure.core :refer :all]))

(deftest a-test
(testing "FIXME, I fail."
(is (= 0 1))))
root@a1be067ee435:/usr/src/living-clojure# lein test

lein test living-clojure.core-test

lein test :only living-clojure.core-test/a-test

FAIL in (a-test) (core_test.clj:7)
FIXME, I fail.
expected: (= 0 1)
actual: (not (= 0 1))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
Tests failed.
root@a1be067ee435:/usr/src/living-clojure#

I guess this is in-keeping with "start with a failing test". Fair enough then. At least the test runs correctly, which is good.

The last bit of checking the baseline project is… will it compile and run as a jar?

root@a1be067ee435:/usr/src/living-clojure# lein uberjar
Compiling living-clojure.core
Created /usr/src/living-clojure/target/uberjar/living-clojure-0.1.0-SNAPSHOT.jar
Created /usr/src/living-clojure/target/uberjar/living-clojure-0.1.0-SNAPSHOT-standalone.jar
root@a1be067ee435:/usr/src/living-clojure# java -jar target/uberjar/living-clojure-0.1.0-SNAPSHOT-standalone.jar
Hello, World!
root@a1be067ee435:/usr/src/living-clojure#

Perfect.

Right. Now I actually need to write some code. I wanna change that main function to read the first argument from the command-line - if present - and say "G'day [value]" (eg: "G'day Zachary"), or if there's no argument, then stick with just "G'day World".

Given that's all very simple, I could just do it with a text editor, but I'll hook an IDE up to this lot too. I googled around and found that VSCode and the Calva Clojure plug-in. I've already set all that up and can't be arsed redoing it for the purposes of this article. One issue I had… and still have… is that Calva is supposed to be able to connect to an external REPL, instead of using its own one. Whilst experimenting trying to get this working, I tweaked my docker create statement to run a headless REPL instead of bash:

docker create --name living-clojure --mount type=bind,source=/mnt/c/src/living-clojure,target=/usr/src/living-clojure --expose 56505 -p 56505:56505 --workdir /usr/src/living-clojure --interactive --tty clojure lein repl :headless :host 0.0.0.0 :port 56505

The new stuff is as follows:

  • I probably don't need the --expose here for what I'm doing, but the -p exposes the container's port 56505 to the host PC (56505 has no significance… it was just one of the ports a previous REPL had used, so I copied that).
  • I start a headless REPL (so basically a REPL as a service, that just listens for REPL connections) that listens on that port 56505
  • I have to use 0.0.0.0 here because when I tried to use 127.0.0.1, that worked fine from within the container, but was no good on the host machine. It took me a bit of googling to crack that one.

I start this one without being interactive:

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ docker start living-clojure
living-clojure
adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$

Doing it this way I am exposing a REPL to other sessions, whilst still being able to bash my way along too:

adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ docker exec --interactive --tty living-clojure lein repl :connect localhost:56505
Connecting to nREPL at localhost:56505
REPL-y 0.4.4, nREPL 0.6.0
Clojure 1.10.1
OpenJDK 64-Bit Server VM 11.0.9.1+1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e

living-clojure.core=> (println "G'day world")
G'day world
nil
living-clojure.core=> quit
Bye for now!
adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$ docker exec --interactive --tty living-clojure /bin/bash
root@222940f1eafd:/usr/src/living-clojure# ls
CHANGELOG.md LICENSE README.md doc project.clj resources src target test
root@222940f1eafd:/usr/src/living-clojure# exit
exit
adam@DESKTOP-QV1A45U:/mnt/c/src/living-clojure$

I could get VSCode on my desktop PC to see this REPL:


But I could not get it to do stuff like running tests, which it was able to do using the built-in REPL. So that sucked. I googled a bit and I am not the only person with this problem when using external REPLs, so I have just decided to give up on that. I'll cheat and use the built-in REPL.

Oh god. I now need to write some Clojure code (I've now caught up with my previous progress, and am typing this article "live" now… Bear with me for a bit).

Time passes…

More time passes and there is swearing…

OK, got there. I abandoned using VSCode and Calva because Calva seems to have this strange idea that when I go to delete things (like extra or misplaced parentheses), that it knows better and doesn't just do what it's frickin' told. It's probably me not knowing some special trick that people accustomed to using it know, but… for the love of god if I ask a frickin' text editor to do something in the edit window, I expect it to just do it even if I'm asking it to do something less than ideal. I will seek out a different plugin later. Once I dropped back to using Notepad++, things went better. I have done a lot of googling in the last two hours though. Sigh.

Anyhow, here are my tests:

(ns living-clojure.core-test
  (:require [clojure.test :refer :all]
    [living-clojure.core :refer :all]))

(deftest test-greet-default-behaviour
  (testing "it should return G'day world if no name is passed"
    (is (= "G'day World" (greet [])))))

(deftest test-greet-by-single-name
  (testing "it should return G'day [name] if just that name is passed"
  (is (= "G'day Zachary" (greet ["Zachary"])))))

(deftest test-greet-by-multiple-name
  (testing "it should return G'day [name] if more than one names are passed"
  (is (= "G'day Zachary" (greet ["Zachary" "Cameron" "Lynch"])))))

And here is the code:

(ns living-clojure.core
  (:gen-class))

(defn greet
  ([args]
    (str "G'day " (or (first args) "World"))))
  
(defn -main
  "I greet someone or everyone"
  [& args]
  (println (greet args)))

And here's the output of the tests, running the code, and compiling and running the code:

root@222940f1eafd:/usr/src/living-clojure# lein test

lein test living-clojure.core-test

Ran 3 tests containing 3 assertions.
0 failures, 0 errors.


root@222940f1eafd:/usr/src/living-clojure# lein run
G'day World


root@222940f1eafd:/usr/src/living-clojure# lein run Zachary
G'day Zachary


root@222940f1eafd:/usr/src/living-clojure# lein run Zachary Cameron Lynch
G'day Zachary


root@222940f1eafd:/usr/src/living-clojure# lein uberjar
Compiling living-clojure.core
Created /usr/src/living-clojure/target/uberjar/living-clojure-0.1.0-SNAPSHOT.jar
Created /usr/src/living-clojure/target/uberjar/living-clojure-0.1.0-SNAPSHOT-standalone.jar


root@222940f1eafd:/usr/src/living-clojure# java -jar target/uberjar/living-clojure-0.1.0-SNAPSHOT-standalone.jar
G'day World


root@222940f1eafd:/usr/src/living-clojure# java -jar target/uberjar/living-clojure-0.1.0-SNAPSHOT-standalone.jar Zachary
G'day Zachary


root@222940f1eafd:/usr/src/living-clojure# java -jar target/uberjar/living-clojure-0.1.0-SNAPSHOT-standalone.jar Zachary Cameron Lynch
G'day Zachary


root@222940f1eafd:/usr/src/living-clojure#

I got a bit snagged on the variadic parameters syntax for a while, largely cos to me that just says "it's a reference", so I need to unlearn that in this context. And then cos I've been staring at the screen too long now, I had a brain-disconnect on only the parameters of main were variadic… by the time they got to greet it was just an array (or a list or whatever Clojure calls it). So due to dumbarsey on my part I variously had the tests passing but the behaviour at runtime slightly off, or the tests failing but the program actually running correctly. What this says is that I probably should have been testing main, not greet. My bad.

OK. It's 22:30 and I've been at this on and off for quite a few hours now. I'm pretty pleased with the exercise as a whole - I learned a lot - but now I want to go shoot things. Parentheses. I want to shoot parentheses. But I guess I will settle for shooting raiders in Fall Out 4.

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

Tuesday 19 January 2016

Floating point arithmetic with decimals

G'day:
As a human... what is the value of z, after you process this pseudocode with your wetware:

x = 17.76
y = 100
z = x * y

Hopefully you'd say "1776". It was not a trick question.

And that's an integer, right? Correct.

CFML

Now... try this CFML code:

x = 17.76;
y  = 100;
z = x*y;

writeOutput(z);

1776 So far so good.

But what about this:

writeOutput(isValid("integer", z));

You might think "YES" (or true if yer on Lucee), however it's "NO".

And this is where young players fall into the trap. They get all annoyed with isValid() getting it wrong, etc. Which, to be fair, is a reasonable assumption with isValid(), but it's not correct in this instance. It's the young player who is mistaken.

If we now do this:

writeOutput(z.getClass().getName());

We get: java.lang.Double

OK, but 1776 can be a Double, sure. But CFML should still consider a Double 1776 as a valid integer, as it should be able to be treated like one. So why doesn't it? What if we circumvent CFML, and go straight to Java:

writeOutput(z.toString());

1776.0000000000002

Boom. Floating point arithmetic inaccuracy.

Never ever ever forget, everyone... when you multiply floating point numbers with decimals... you will get "unexpected" (but you should pretty much expect it!) floating point accuracy issues. This is for the perennial reason that what's easy for us to express in decimal is actually quite hard for a computer to translate into binary accurately.

Aside: we were chatting about all this on the CFML Slack channel this morning, and one person asked "OK, so how come 17.75 x 100 works and 17.76 x 100 does not?". This is because a computer can represent 0.75 in binary exactly (2-1 + 2-2), whereas 0.76 can only be approximated, hence causing the "issue".

The problem really is that CFML should simply output 1776.0000000000002 when we ask it, and it should not try to be clever and hide this stuff. Because it's significant information. Then when the young player output the value, they'd go "oh yeah, better round that" or whatever they need to do before proceeding. CFML is not helping here.

This is pretty ubiquitous in programming. Let's have a trawl through the various languages I can write the simplest of code in:

JavaScript


x = 17.76;
y = 100;
z = x * y

console.log(z);


>node jsVersion.js
1776.0000000000002

>

JS just does what it's told. Unsurprisingly.

Groovy


x = 17.76
y = 100
z = x * y
println "x * y: " + z

println "x: " + x.getClass().getName()
println "y: " + y.getClass().getName()
println "z: " + z.getClass().getName()
println "z: " + z.toString()


>groovy32 groovyVersion.groovy

x * y: 1776.00
x: java.math.BigDecimal
y: java.lang.Integer
z: java.math.BigDecimal
z: 1776.00
>


This is interesting. Whilst Groovy keeps the result as a float (specifically a BigDecimal) - which is correct - it truncates it to the total number of decimal places expressed in its factors. That's how I was taught to do it in Physics at school, so I like this. This second example makes it more clear:

x = 3.30
y = 7.70
z = x * y
println "x * y: " + z

println "x: " + x.getClass().getName()
println "y: " + y.getClass().getName()
println "z: " + z.getClass().getName()
println "z: " + z.toString()


>groovy32 more.groovy
x * y: 25.4100
x: java.math.BigDecimal
y: java.math.BigDecimal
z: java.math.BigDecimal
z: 25.4100
>

In 3.30 and 7.70 there are four decimal places expressed (ie: two for each factor), so Groovy maintains that accuracy. Nice!


Java


import java.math.BigDecimal;

class JavaVersion {

    public static void main(String[] args){
        double x = 17.76;
        int y = 100;
        System.out.println(x*y);
        
        BigDecimal x2 = new BigDecimal(17.76);
        BigDecimal y2 = new BigDecimal(100);
        System.out.println(x2.multiply(y2));
        
    }
}

Here I added a different variation because I was trying to see why the Groovy code behaved the way it did, but it didn't answer my question. I suspected that perhaps it was a BigDecimal thing how it decided on the accuracy of the result, but it wasn't:


>java JavaVersion
1776.0000000000002
1776.000000000000156319401867222040891647338867187500

>

This is a good demonstration of how a simply base-10 decimal fraction is actually an irrational number in binary.

Sunday 17 January 2016

Stepping looping

G'day:
Yeah, this is an odd thing to write about, and there's no trick to it: I really do mean this sort of thing:

for i=1 to 10 step 2

I got to thinking about this because of a comment from Chris Dawes on my article Lucee 5 beta: <cfloop> tweak, in which he sung the praises of the CFML tag equivalent of that general construct:

<cfloop index="i" from="1" to="10" step="2">

This is fine for a view, but I'd steer clear of it in my business logic. But what would I use instead? The obvious answer is this:

for (i=1; i <= 10; i+=2)

But these days I try to eschew generic looping like that, in favour of a more purposeful iteration exercise using iteration methods. If I was iterating a collection to remap data, I'd use map(); if I was doing so to translate the collection data to another data structure or object, I'd reduce(); if I was getting rid of stuff I'd filter(), and - if the language allowed it - if I was checking to see if one or any of the collection elements fulfilled some criteria, I'd use some() or every(). It's really seldom that one wants to loop "for the hell of it", if one thinks about it. It's an exercise in processing data or checking data.

Obviously in a view one is likely to be just outputting data from the data collection, so using a general loop is as good as anything. Also if one is skipping records... a stepped general purpose loop is perhaps better than using like an array-based loop and conditionally continuing the loop or some nonsense like that. All good.

But this got me thinking... let's say I was:

  1. writing business logic;
  2. wanting to step over array elements;
  3. still wanted to use array iteration methods.

This is more a mental exercise than a programming one, but it just happened to pique my interest.

CFML

In CFML this is dead easy. Here's an example:

numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"];

oddNumbers = numbers.filter(function(_,i){return i mod 2;});

writeDump(oddNumbers);

Here I'm using ColdFusion 12's CLI, and the results are as follows:

C:\src\cfml\languageComparison\steppedLoop>cf cfmlVersion.cfm
array

1) tahi
2) toru
3) rima
4) whitu
5) iwa

C:\src\cfml\languageComparison\steppedLoop>

filter() allows us to create an array of every second element simply by doing the usual mod operation. This gives us a new array.

If we wanted to actually render the elements as a string, we'd just chain a reduce() call onto that:

oddNumbersAsString = numbers.filter(function(_,i){return i mod 2;}).reduce(function(combined, oddNumber){
    return "#combined##oddNumber##chr(10)#";
}, "");
CLI.writeLn(oddNumbersAsString);


C:\src\cfml\languageComparison\steppedLoop>cf cfmlVersion.cfm
tahi
toru
rima
whitu
iwa

C:\src\cfml\languageComparison\steppedLoop>

Or, hey: not get so dogmatic about using collection iteration methods, and just leverage a list method:

oddNumbersAsString = numbers.filter(function(_,i){return i mod 2;}).toList(chr(10));

(yields the same output)

I turned my mind to other languages to compare.

JavaScript

Same as CFML really. Just some "spelling" differences:

oddNumbersAsString = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"]
    .filter(function(_,i){return (i+1) % 2;})
    .reduce(function(combined, oddNumber){
        return combined + oddNumber + String.fromCharCode(10);
    }, "");

console.log(oddNumbersAsString);

Output:


C:\src\cfml\languageComparison\steppedLoop>node jsVersion.js
tahi
toru
rima
whitu
iwa


C:\src\cfml\languageComparison\steppedLoop>

PHP

PHP is a bit disappointing here for a coupla reasons. Firstly PHP is still at its roots still procedural, so the code is intrinsically more clunky, plus it's collection iteration methodsfunctions aren't implemented that thoughtfully, so I have to jump through some intermediary hoops:
$numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"];

$indexedNumbers = array_map(null, range(0, count($numbers)-1), $numbers);
$indexedOddNumbers = array_filter(
    $indexedNumbers,
    function($number){
        return ($number[0]+1) % 2;
    }
);
$reindexedIndexedOddNumbers = array_values($indexedOddNumbers);

$oddNumbers = array_map(
    function($number){
        return $number[1];
    },
    $reindexedIndexedOddNumbers
);

$oddNumbersAsString = array_reduce(
    $oddNumbers,
    function($combined, $oddNumber){
        return $combined . $oddNumber . PHP_EOL;
    },
    ""
);
echo $oddNumbersAsString;

The issues here are:

  • we want to filter on the array index position, but PHP doesn't think to pass this to the array_filter()'s callback, so I need to push an index into the data using array_map() and range(). One good thing here is that array_map(), if not given a callback then it just maps all the arrays it's passed together into sub arrays. Which suits my purposes here.
  • Then I can use array_filter() to do the actual filtering, but array_filter() is a bit sh!t for a second time because it leaves a sparse array after it filters. So after I filter I don't have a new array with elements 0-4, I have one with elements 0,2,4,6,8 (!!!!). How bloody stupid is that? I guess this is down to PHP's array implementation being quite rubbish and basically being "kinda an array, kinda a struct, kinda - as a result - neither".
  • I need to fix this by creating a new array with just the values.
  • As I said PHP is procedural, so I can't chain stuff. I could nest it, but that makes for awful, inside-out code. So I use individual statements and intermediary variables.
  • Having filtered I still need to get my data elements back to "normal" by getting rid of the index I embedded in them. Another call to array_map() to undo what I did before.
  • And finally I can reduce things back down to a string.
Way too much horsing around there. Note: I know this is - as a result - a contrived example. There's no way a sane person would do this sort of thing. In PHP, one's better off eschewing the array iteration methods and just using general forEach() loops. Old school, but they work.

Saturday 10 January 2015

Getting Clojure installed and running and "G'day World"-ing

G'day:
This is just another one of those article about my experience with working out how to get a language operational in my dev environment. All it really serves to do is to demonstrate there's not much to it, so it should not be a "barrier to entry" if you decide to have a look at Clojure. I am way too much a newbie with it to be offering anything actually insightful here though.

Obviously that said if one's just wanting to give it a bash, there's online REPLs about the place (eg: Try Clojure) so no need to install it, but their scope will be limited, and for some reason I prefer to have stuff running locally.

I've worked through the Try Clojure tutorial before - ages ago - but other than that know nothing about it other than it seems to be overly in love with parentheses. Here goes...