04 Jun 2016
If you follow me on twitter, you probably notice that I usually post links of things that I find interesting, along with possible rants and cat/dog pictures. However I think twitter is a nice place to post things, but I still think it doesn’t get too much attention: a twitter feed is an stream of interesting stuff/rants/anything else that I usually don’t bother to keep something like an inbox zero approach.
One thing however that I try to read it all are e-mails besides personal e-mail, I try only to subscribe to interesting newsletter. So, I decided to create a newsletter: I’m still not sure to whether I should post or not but most necessarily trivia which is non-coding stuff: as I still have this blog for this purpose.
If you want to subscribe here is the link: https://www.getrevue.co/profile/rlmflores.
25 Feb 2016
The following blog post is about the book Clojure Applied written by Ben Vandgrift and Alex Miller. I have not received a free copy of the book to review it.
As Clojure is becoming more and more popular these days, we have a lot of programmers learning to write code in Clojure, and almost all books tell you how each part works: functions, macros, maps, lists, records, multi-methods, agents, refs, transducers, reducers. But how can you apply all these concepts together ?
I like to think of Clojure like a infinite Lego box: you can build your system using the pieces, but can you make smart choices to select the best tool for each part of your system ? Some parts appear to be similar: lists and vectors, maps and records, multi-methods and protocols, but how do I select one instead of another ?
Clojure Applied is the first Clojure Book I read which wasn’t targeted for people initiating in the Clojure world, so it considers that you have some experience. I like to think of it like a book of answers to questions that if you deal with Clojure frequently you will probably wonder about. I particularly liked the chapter 6 “Creating Components”: on how to structure your application in an organized way.
Another cool thing is that it not only covers features of the language but also some well-known parts of the ecosystem like the core async library, Stuart Sierra’s component library, the schema library and transit and EDN as data serializers. It also covers how to deploy your code to a production server using a platform, a IAAS or using your own servers.
I strongly recommend you to not read this book cover to cover, but to try to write a small application after each chapter applying what you have learned: I did it this way and I really think it was worth it.
01 Feb 2016
If you’re bootstraping a new Clojure application, you would run this command:
lein new app my-awesome-app
And then Leiningen would generate a directory containing the bare minimum to make your application build (with a Hello World example). However, everytime I did that for experimenting purposes, I found that I’ve always added two libraries: Midje and Schema, so to help me stop doing this repetitive work, I created a template (aka generator) for that. Doing this, I can bootstrap an application with both libraries by just running this command:
lein new app-with-midje my-awesome-app
Midje
Midje is a testing framework for Clojure. I like the way tests are written in Midje:
(facts "average of items
(fact "average of 1 item is itself"
(average 1) => 1)
(fact "average of 2 items is the minor item plus half the distance"
(average 1 5) => 3)
(tabular
(fact "calculates average"
(average ?a ?b ?c) => ?average)
?a ?b ?c ?average
1 9 11 7
3 12 30 15
30 60 90 60))
They’re very similar to what you would describe as input and output examples of a function. Also, I think tabular examples shows clearly what you would expect on each case, without having to duplicate code on each fact. It is also possible to mock and redef other functions on a test scope, so you can create isolated unit tests, and it is also possible to build your own checks, so you can create better tests based on your domain. So, in general, I consider Midje to be a great testing library to work with.
Schema
Clojure is not a typed language, so it is fairly common to see maps being used as a kind of typed data. Suppose you have this map describing a person:
(def person {:name "Joe Doe"
:age 45
:team "Blackburn Rovers"})
(def another-person {:name "Jane Doe"
:age 48})
For some reason, you want to give Joe
and Jane
a football jersey of their favourite football team. However, you forgot that this field may be not filled, as it is not mandatory for everyone to have a football team, so you wrote your function like this:
(defn ship-football-shirt [customer]
(ship-shirt customer (buy-shirt (:team customer)))
As you forgot that it is a required, when you call the function with Joe
, it works because he has a football team, but when you for Jane
, it gives you a NullPointerException
(Clojure normally raises a NPE
you treat a nil
like a map). How to get over this kind of issue ?
To help deal with this kind of problem, there is a library called Schema. After specifying it on your project.clj, you can instantiate on your namespaces and use this way
(require '[schema.core :as s])
(def teams #{"Blackburn Rovers" "Leicester United" ... } )
(def all-teams-ever (s/enum teams)) ; I'm not writing the name of all teams ever
(def Person {(s/required-key :name) s/Str
(s/required-key :age) s/Int
:team all-teams-ever})
(s/validate Person person)
To declare a map schema, you should insert the keys and the accepted values for each key. Optionally, you can say that a key is required. If you call the s/validate
function
with a schema and a map, it will try to validate the type of each value and also the presence of all required keys, throwing an exception in case something doesn’t validate. On our example, it requires a person to have a :name
key with any string value, an :age
key with any integer value and optionally a team with any value specified in the set
teams
.
To help fix our problem, we can create a derived schema call FootballFan
:
(def FootballFan (assoc (dissoc Person :team) (s/required-key :team) all-teams-ever))
And then you can rewrite ship-football-shirt
this way:
(s/defn ship-football-shirt [customer :- FootballFan]
(ship-shirt customer (buy-shirt (:team customer)))
The :-
symbol means that the customer symbol should be validated with that schema. When you call the function above inside the body of the macro (s/with-fn-validation )
it will trigger an exception. Why is this function validation optional ? Performance reasons mostly: makes sense to have this check turned on throughout your code on a testing environment but on production it might spend valuable time checking schemas (but a validate
call on strategic places like before inserting something to a database or when you’re receiving data from and HTTP request definitely makes sense).
Will it avoid receiving an exception ? Absolutely not, you will still get an exception, but this time you will receive a specific error telling you that Jane is missing that key/value. This kind of validation avoid not only strange errors, but you also don’t need to program defensively inside ship-football-shirt
(as you specified that you only accept maps that validate with that schema).
The App with Midje template
As I use both libraries on almost every application, I created a template for that: a set of files and folders generated given a project name. So, after adding it to my lein profile,
I can call lein new app-with-midje awesome-project
and it will generate a project.clj with both libraries, an APACHE V2
license,a README
, a .gitignore
file, a core.clj
file to write the application (and a correspondent test file), a repl.clj
file that serves as a REPL wrapper to load libraries and functions (like an the autotesting namespace) for general REPL
there.
To use the generator, add on your ~/.lein/profiles.clj
the plugin and the version (in case you already have the plugin vector, just append it):
{:user {:plugins [[app-with-midje/lein-template "0.1.0"]]}}
And then, to generate the application:
lein new app-with-midje my-awesome-app
You can also check the code for the template on Github.