Two weeks ago I facilitated an introduction to TDD in a Scala Developers Barcelona event.
First, I gave a very short introductory talk about TDD, (these are the slides).
After the talk, I coded a simple exercise to help the attendants get a feeling of the TDD flow.
I tried to stress the importance of creating a list of examples first and choosing the next test so that the code could grow in small steps. I also talked about wishful programming, going from red to green quickly and the importance of refactoring to sustainable TDD.
I used Clojure to code the exercise.
I feel that, somehow, using Clojure got in the way of the message I wanted to transmit. Next time I'll use a language which is more familiar to all the attendants.
This is my solution to the exercise in Clojure where you'll find commits after each passing test and each refactoring, so you can follow the process.
Finally, we started doing TDD with a simple kata.
Ignasi, Jordi and I were all the time visiting the different pairs, helping with doubts about TDD or Scala and commenting with them how their solutions were growing test after test.
I think that during this time I had the chance to clarify some things that hadn't come across before because of the lack of familiarity with Clojure.
At the end, we had a short debate about TDD.
I'd like to thank the Scala Developers Barcelona organizers for inviting me to facilitate this dojo. I had a great time and I hope that, the attendants could get something useful from it in spite of my using Clojure for the first example.
I'd like to also thank all the attendants for coming and Álvaro for telling the Scala Developers Barcelona organizers about me.
Record of experiments, readings, links, videos and other things that I find on the long road.
Registro de experimentos, lecturas, links, vídeos y otras cosas que voy encontrando en el largo camino.
Saturday, February 28, 2015
Friday, February 27, 2015
Wednesday, February 18, 2015
Interesting Talk: "Simplicity Matters"
I've just watched this wonderful talk by Rich Hickey:
Thanks to Pablo Díez for recommending it to me.
Monday, February 16, 2015
Clojure Developers Barcelona: Introductory talk about Clojure functions
Last week I gave a talk about Clojure functions for some of the Clojure Developers Barcelona study group members.
This is the the code I showed on Lightable to explain a bit about Clojure functions:
I had a great time and learned a lot preparing this talk.
During the talk I mentioned some destructuring techniques I had explained more deeply in a previous talk.
This time there were many more attendants than in previous events of the group which is really great.
Thanks all for coming!
I hope we'll go on learning together in next events.
This is the the code I showed on Lightable to explain a bit about Clojure functions:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;-------------- | |
;; | |
;; A bit about Clojure functions | |
;; | |
;;-------------- | |
;; Functions are first class values in Clojure. | |
;;-------------- | |
;; 1. fn form | |
;;-------------- | |
;; Functions are created using the fn form which also folds the semantics of let and do | |
(fn [x] | |
(inc x)) | |
((fn [x] | |
(inc x)) 3) | |
;(fn [x] <- fn accepts a let-style binding vector so | |
; all the semantics we explained in the destructuring talk can be used here | |
; (println x) <- The forms following the binding vector are the body of the function. | |
; (dec x) This body is placed in an implicit do form, so each function body may | |
; (println x) contain any number of forms and the last form in the body supplies the | |
; 8) result returned to the function caller | |
(do | |
(println "a") | |
(dec 4)) | |
((fn [x] | |
(println x) | |
(dec x) | |
(println x) | |
8) 10) | |
;; The arguments to a function are matched to each name in the destructuring form based on | |
;; their position in the calling form | |
((fn [x] (inc x)) 2) | |
; It would be equivalent to this let form: | |
(let [x 2] | |
(inc x)) | |
;;-------------- | |
;; 2. Arity. | |
;;-------------- | |
;;-------------- | |
;; 2.1 The arity of a function is strict | |
;;-------------- | |
;((fn [x] (inc x)) 2 4) ;;-> clojure.lang.ArityException: Wrong number of args (2) passed... | |
;;-------------- | |
;; 2.2 Functions with multiple arities | |
;;-------------- | |
(def add ; <- giving it a name in the current namespace | |
(fn add-self-reference ; <- optional internal name (inside body) | |
([x] (add-self-reference x 1)) | |
([x y] (+ x y)))) | |
; Function calls are dispatched on the number of arguments. The arity is still strict, though. | |
(add 4) | |
(add 4 3) | |
;(add 4 3 8) ;-> clojure.lang.ArityException: Wrong number of args (3) passed... | |
; Notice the optional name given to the function 'add-self-reference'. | |
; This optional first argument to fn can be used within the function's body to refer to itself. | |
;;-------------- | |
;; 3. defn macro | |
;;-------------- | |
; defn is a macro that encapsulates the functionality of def and fn to concisely define functions | |
; that are named and registered in the current namespace with a given name | |
; This definition would be equivalent to the previous one; | |
(defn add | |
([x] (add x 1)) | |
([x y] (+ x y))) | |
(add 4) | |
(add 4 3) | |
;;-------------- | |
;; 3.1 Doc strings | |
;;-------------- | |
(defn pow | |
"it returns 'base' to the power of 'exp'" | |
[base exp] | |
(reduce * (repeat exp base))) | |
(doc pow) | |
;;-------------- | |
;; 3.2 Destructuring function arguments | |
;;-------------- | |
; The defn macro reuses the fn for which reuses the let form | |
; to bind function arguments for the scope of the function's body. | |
; This means that everything we saw in the previous talk about | |
; destructuring can be done to function arguments. | |
(defn neighbors [[x-cell y-cell]] | |
(set (for [x (range (dec x-cell) (+ x-cell 2)) | |
y (range (dec y-cell) (+ y-cell 2)) | |
:when (not (and (= x x-cell) (= y y-cell)))] | |
[x y]))) | |
(neighbors [0 0]) | |
; This makes possible some useful idioms for function: | |
;;-------------- | |
;; 3.2.1 Variadic arguments (using sequential destructuring) | |
;;-------------- | |
(defn greetings [greeeting & people] | |
(map #(str greeeting " " %) people)) | |
(greetings "hola" "koko" "moko") | |
(defn +-mine [& args] ; variadic | |
(apply + args)) | |
(+-mine 1 2 3) | |
(defn print-separated-by-spaces [& args] | |
(clojure.string/join " " args)) | |
(print-separated-by-spaces 1 5 "hola") | |
;;-------------- | |
;; 3.2.2 Keyword arguments a la Python (using map destructuring) | |
;;-------------- | |
; With them you can define a function that can accept many arguments, some of which | |
; might be optional and some of which might have defaults. | |
; You can also avoid forcing a particular argument ordering. | |
(defn fn-with-named-parameters [{name :name}] | |
name) | |
(fn-with-named-parameters "a") | |
(fn-with-named-parameters {:name ["a"]}) | |
(fn-with-named-parameters {:name "b"}) | |
(defn more-complete-fn-with-named-parameters | |
[req1 req2 ; <- required params | |
& {:keys [a b c d e] ; <- optional params | |
:or {a 1 c 3 d 0} ; <- a, c and d have default values | |
; b and e will take nil if not specified on call | |
:as mapOfParamsSpecifiedOnCall}] ; it'll be nil if no optional parameters are specified on call | |
(print-separated-by-spaces req1 req2 mapOfParamsSpecifiedOnCall a b c d e)) | |
(more-complete-fn-with-named-parameters 1 2) | |
(more-complete-fn-with-named-parameters 1 2 :a "a" :c "c" :d "d") | |
; call ordering doesn't matter for keyword arguments | |
(more-complete-fn-with-named-parameters 1 2 :d "d" :c "c" :a "a") | |
(more-complete-fn-with-named-parameters 1 2 :d "d" :c "c" :a "a" :b 3) | |
(more-complete-fn-with-named-parameters 1 2 :d "d" :c "c" :a "a" :e 85 :b "koko") | |
(more-complete-fn-with-named-parameters 1 2 :d "d" :c "c" :a "a" :b 3) | |
;;-------------- | |
;; 3.3 defn- macro | |
;;-------------- | |
; Same as defn, but yielding a non-public def | |
;;-------------- | |
;; 4. Preconditions and postconditions | |
;;-------------- | |
; fn provides support for preconditions and postconditions which are used to perform assertions | |
; on functions arguments and results, respectively. | |
; They are valuable for testing and to enforce function invariants | |
(Math/sqrt 4) | |
(Math/sqrt -4) | |
(defn root-square [x] | |
{:pre [(or (zero? x) (pos? x))]} | |
(Math/sqrt x)) | |
(root-square 4) | |
(root-square 0) | |
;(root-square -4) ; java.lang.AssertionError: Assert failed: (>= x 0) | |
(try (root-square -4) | |
(catch AssertionError e | |
(.getMessage e))) | |
(defn constrained-fn [f x y] | |
{:pre [(pos? x) (neg? y)] | |
:post [(> % -3)]} ; <- postcondition on the result (%) of the function | |
(f x y)) | |
(constrained-fn #(* %1 %2) 2 -1) | |
;(constrained-fn #(* %1 %2) -2 -1) ;java.lang.AssertionError: Assert failed: (pos? x) | |
;(constrained-fn #(* 5 %1 %2) 2 -1) ; java.lang.AssertionError: Assert failed: (> % -3) | |
;(set! *assert* false) ; <- it works at compiling time not at run time... | |
;(root-square -4) ; NaN in this case | |
;; Check also clojure.core.contracts -> https://github.com/clojure/core.contracts | |
;;-------------- | |
;; 5. Function literals | |
;;-------------- | |
; When you need to define an anonymous function (especially a very simple one) | |
; they are the most concise way to do it. | |
;;-------------- | |
;; 5.1 Syntactic sugar on fn | |
;;-------------- | |
(map (fn [x] (Math/pow x 2)) [1 2 3]) | |
(map #(Math/pow %1 2) [1 2 3]) | |
(read-string "#(Math/pow %1 2)") | |
;;-------------- | |
;; 5.2 They are not exactly the same, though | |
;;-------------- | |
;;-------------- | |
;; 5.2.1 No implicit do form | |
;;-------------- | |
; fn and all its derivatives (defn, defn-,...) wrap their function bodies | |
; in an implicit do form. | |
; Allowing to do things like: | |
(fn [x y] | |
(println (str x " " y)) | |
(+ x y)) | |
; The equivalent function literal requires an explicit do form: | |
#(do | |
(println (str %1 " " %2)) | |
(+ %1 %2)) | |
;;-------------- | |
;; 5.2.2 Arity and arguments specified using unnamed position symbols | |
;;-------------- | |
; The literal uses unnamed positional symbols, where %1 is the first argument, | |
; %2 the second, etc. | |
(map #(Math/pow %1 %2) [1 2 3] [1 2 3]) | |
; The highest positional arity symbol defines the arity of the function. | |
(#(inc %3) "ignored" "ignored" 4) | |
; You can use % to refer to the first parameter (prefer the shorter notation in general) | |
(map #(Math/pow % 2) [1 2 3]) | |
(map #(Math/pow % %2) [1 2 3] [1 2 3]) | |
; You can define a variadic function and refer to the rest of parameters | |
((fn [x & rest] | |
(- x (apply + rest))) 10 1 2 3) | |
(#(- % (apply + %&)) 10 1 2 3) | |
;;-------------- | |
;; 5.2.3 Function literals can't be nested | |
;;-------------- | |
(((fn [x] | |
(fn [y] | |
(str x " " y))) 2) 3) | |
;((#(#(str % " " %)) 2) 3) ; -> java.lang.IllegalStateException: Nested #()s are not allowed | |
;; References: | |
;; Clojure Programming, Practical Lisp for the Java World. Chas Emerick, Brian Carper, Christophe Grand | |
;; | |
;; Clojure Docs defn -> https://clojuredocs.org/clojure.core/defn | |
;; | |
;; Clojure Docs defn- -> https://clojuredocs.org/clojure.core/defn- | |
;; | |
;; Clojure’s :pre and :post -> http://blog.fogus.me/2009/12/21/clojures-pre-and-post/ | |
;; To learn more: | |
;; Clojure Programming, Practical Lisp for the Java World. Chas Emerick, Brian Carper, Christophe Grand | |
;; | |
;; A first take on contracts in Clojure -> http://ianrumford.github.io/blog/2012/11/17/first-take-on-contracts-in-clojure/ | |
;; | |
;; Contracts programming for Clojure -> https://github.com/clojure/core.contracts |
During the talk I mentioned some destructuring techniques I had explained more deeply in a previous talk.
This time there were many more attendants than in previous events of the group which is really great.
Thanks all for coming!
I hope we'll go on learning together in next events.
Thursday, February 12, 2015
Kata: Password validator in Clojure
I've just done the Password validator kata in Clojure.
It's a basic kata that, in my opinion, is very useful to think about how we choose the next test so that it helps us grow the code in small steps.
In this kata you have to validate a password.
The conditions that it has to fulfill to be valid are:
and this is the final version of the code:
I used a mix of TDD and work on the REPL to code it.
To document the process I commited the code after every passing test and every refactoring and also commited the REPL history.
You can find the commits step by step here and the code in this repository in GitHub.
It's a basic kata that, in my opinion, is very useful to think about how we choose the next test so that it helps us grow the code in small steps.
In this kata you have to validate a password.
The conditions that it has to fulfill to be valid are:
- It should have more than 6 characters
- It should contain at least one upper case character
- It should contain at least one lower case character
- It should contain at least one underscore
- It should contain at least one numeric character
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns password-validator.core-test | |
(:use midje.sweet) | |
(:use [password-validator.core])) | |
(facts | |
"about password validator" | |
(fact | |
"it accepts a valid password" | |
(valid? "_Ab3ccc") => true) | |
(fact | |
"it rejects passwords with no more than 6 characters" | |
(valid? "_Ab3cc") => false) | |
(fact | |
"it rejects passwords that do not contain | |
at least one upper case character" | |
(valid? "_ab3ccc") => false) | |
(fact | |
"it rejects passwords that do not contain | |
at least one lower case character" | |
(valid? "_AB3CCC") => false) | |
(fact | |
"it rejects passwords that do not contain | |
at least one underscore character" | |
(valid? "aAB3CCC") => false) | |
(fact | |
"it rejects passwords that do not contain | |
at least one numeric character" | |
(valid? "_Abcccc") => false)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns password-validator.core) | |
(defn- has-more-than-6-chars? [password] | |
(> (count password) 6)) | |
(defn- any? [pred coll] | |
(not= nil (some pred coll))) | |
(def ^:private contains-at-least-one-upper-case-char? | |
(partial any? #(Character/isUpperCase %))) | |
(def ^:private contains-at-least-one-lower-case-char? | |
(partial any? #(Character/isLowerCase %))) | |
(def ^:private contains-at-least-one-underscore-char? | |
(partial any? #{\_})) | |
(def ^:private contains-at-least-one-numeric-char? | |
(partial any? #(Character/isDigit %))) | |
(def ^:private conditions-to-be-valid | |
[has-more-than-6-chars? | |
contains-at-least-one-upper-case-char? | |
contains-at-least-one-lower-case-char? | |
contains-at-least-one-underscore-char? | |
contains-at-least-one-numeric-char?]) | |
(defn valid? [password] | |
(every? #(% password) conditions-to-be-valid)) |
To document the process I commited the code after every passing test and every refactoring and also commited the REPL history.
You can find the commits step by step here and the code in this repository in GitHub.
Friday, February 6, 2015
Using underscore.js to simplify a JavaScript code
I'd like to show how using underscore.js library can simplify a JavaScript code.
This is the original code:
It wasn't tested, so I wrote some tests before start refactoring it:
Once I had the tests in place, I separated the responsibility of converting strings into dates from the traversing of the object:
and extracted it to another factory:
that I could directly test:
Finally I used underscore.js library to simplify all the JavaScript plumbing that was happening in the object traversing inside DateStringsInsideObjectsIntoDates convert function:
By separating the traversing and conversion responsibilities and by using underscore.js, I managed to get to a much more readable version of the code.
This is the original code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.factory('DateStringsInsideObjectsIntoDates', [ | |
function () { | |
function convertDateStringsToDates(input) { | |
var regexIso8601 = /^(\d{4}|\+\d{6})(?:-(\d{2})(?:-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})\.(\d{1,})(Z|([\-+])(\d{2}):(\d{2}))?)?)?)?$/; | |
if (typeof input !== "object") { | |
return input; | |
} | |
for (var key in input) { | |
if (!input.hasOwnProperty(key)) continue; | |
var value = input[key]; | |
var match; | |
// Check for string properties which look like dates. | |
if (typeof value === "string" && (match = value.match(regexIso8601))) { | |
var milliseconds = Date.parse(match[0]) | |
if (!isNaN(milliseconds)) { | |
input[key] = new Date(milliseconds); | |
} | |
} else if (typeof value === "object") { | |
convertDateStringsToDates(value); | |
} | |
} | |
return input; | |
} | |
return { | |
convert: convertDateStringsToDates | |
}; | |
} | |
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe( | |
'Conversion of strings in objects representing a date into dates', | |
function () { | |
describe( | |
'DateStringsInsideObjectsIntoDates', | |
function () { | |
var DateStringsInsideObjectsIntoDates, | |
stringRepresentingDate = "2014-03-20T14:02:41.000000Z"; | |
beforeEach(module("project")); | |
beforeEach(inject(function (_DateStringsInsideObjectsIntoDates_) { | |
DateStringsInsideObjectsIntoDates = _DateStringsInsideObjectsIntoDates_; | |
})); | |
it("only converts strings representing a date when they are inside an object", | |
function () { | |
var stringRepresentingDate = "2014-03-20T14:02:41.000000Z"; | |
expect( | |
DateStringsInsideObjectsIntoDates.convert(stringRepresentingDate) | |
).toEqual(stringRepresentingDate); | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({ | |
a: stringRepresentingDate | |
}) | |
).toEqual({ | |
a: new Date(Date.parse(stringRepresentingDate)) | |
} | |
); | |
} | |
); | |
it("strings not representing a date inside an object are ignored", | |
function () { | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({ | |
a: "koko" | |
}) | |
).toEqual({ | |
a: "koko" | |
} | |
); | |
} | |
); | |
it("converts strings representing a date inside an object with any level of nesting", | |
function () { | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({ | |
a: "koko", | |
b: { | |
c: stringRepresentingDate | |
}, | |
d: 4 | |
}) | |
).toEqual({ | |
a: "koko", | |
b: { | |
c: new Date(Date.parse(stringRepresentingDate)) | |
}, | |
d: 4 | |
} | |
); | |
} | |
); | |
it("ignores empty objects", function () { | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({}) | |
).toEqual({}); | |
} | |
); | |
} | |
); | |
} | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.factory('DateStringsInsideObjectsIntoDates', [ | |
function () { | |
function convertDateStringsToDates(input) { | |
if (typeof input !== "object") { | |
return input; | |
} | |
for (var key in input) { | |
if (!input.hasOwnProperty(key)) continue; | |
var value = input[key]; | |
if (typeof value === "string") { | |
input[key] = convertDateStringToDate(value); | |
} else if (typeof value === "object") { | |
convertDateStringsToDates(value); | |
} | |
} | |
return input; | |
} | |
return { | |
convert: convertDateStringsToDates | |
}; | |
function convertDateStringToDate(str) { | |
var regexIso8601 = /^(\d{4}|\+\d{6})(?:-(\d{2})(?:-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})\.(\d{1,})(Z|([\-+])(\d{2}):(\d{2}))?)?)?)?$/, | |
match = str.match(regexIso8601), | |
milliseconds; | |
if (!match) { | |
return str; | |
} | |
milliseconds = Date.parse(match[0]) | |
if (!isNaN(milliseconds)) { | |
return new Date(milliseconds); | |
} | |
return str; | |
} | |
} | |
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.factory('StringToDate', [ | |
function () { | |
function convertDateStringToDate(str) { | |
var regexIso8601 = /^(\d{4}|\+\d{6})(?:-(\d{2})(?:-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})\.(\d{1,})(Z|([\-+])(\d{2}):(\d{2}))?)?)?)?$/, | |
match = str.match(regexIso8601), | |
milliseconds; | |
if (!match) { | |
return str; | |
} | |
milliseconds = Date.parse(match[0]) | |
if (!isNaN(milliseconds)) { | |
return new Date(milliseconds); | |
} | |
return str; | |
} | |
return { | |
convert: convertDateStringToDate | |
}; | |
} | |
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe( | |
'Conversion of strings in objects representing a date into dates', | |
function () { | |
describe('StringToDate', | |
function () { | |
var StringToDate; | |
beforeEach(module("project")); | |
beforeEach(inject(function (_StringToDate_) { | |
StringToDate = _StringToDate_; | |
})); | |
it("changes a string representing a date in the desired format into a date", | |
function () { | |
expect( | |
StringToDate.convert("2014-03-20T14:02:41.000000Z") | |
).toEqual(new Date(Date.parse("2014-03-20T14:02:41Z"))); | |
expect( | |
StringToDate.convert("2014-03-20T14:02:41.000000") | |
).toEqual(new Date(Date.parse("2014-03-20T14:02:41Z"))); | |
} | |
); | |
it("leaves unchanged strings not representing a date in the desired format", | |
function () { | |
expect( | |
StringToDate.convert("koko") | |
).toEqual("koko"); | |
expect( | |
StringToDate.convert("2014/03/20T14") | |
).toEqual("2014/03/20T14"); | |
expect( | |
StringToDate.convert("2014/03/20T14:02:41.000000Z") | |
).toEqual("2014/03/20T14:02:41.000000Z"); | |
expect( | |
StringToDate.convert("2014-03-20T14-02-41.000000Z") | |
).toEqual("2014-03-20T14-02-41.000000Z"); | |
expect( | |
StringToDate.convert("2014-03-2014:02:41.000000Z") | |
).toEqual("2014-03-2014:02:41.000000Z"); | |
} | |
); | |
} | |
); | |
describe( | |
'DateStringsInsideObjectsIntoDates', | |
function () { | |
var DateStringsInsideObjectsIntoDates, | |
stringRepresentingDate = "2014-03-20T14:02:41.000000Z"; | |
beforeEach(module("project")); | |
beforeEach(inject(function (_DateStringsInsideObjectsIntoDates_) { | |
DateStringsInsideObjectsIntoDates = _DateStringsInsideObjectsIntoDates_; | |
})); | |
it("only converts strings representing a date when they are inside an object", | |
function () { | |
var stringRepresentingDate = "2014-03-20T14:02:41.000000Z"; | |
expect( | |
DateStringsInsideObjectsIntoDates.convert(stringRepresentingDate) | |
).toEqual(stringRepresentingDate); | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({ | |
a: stringRepresentingDate | |
}) | |
).toEqual({ | |
a: new Date(Date.parse(stringRepresentingDate)) | |
} | |
); | |
} | |
); | |
it("strings not representing a date inside an object are ignored", | |
function () { | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({ | |
a: "koko" | |
}) | |
).toEqual({ | |
a: "koko" | |
} | |
); | |
} | |
); | |
it("converts strings representing a date inside an object with any level of nesting", | |
function () { | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({ | |
a: "koko", | |
b: { | |
c: stringRepresentingDate | |
}, | |
d: 4 | |
}) | |
).toEqual({ | |
a: "koko", | |
b: { | |
c: new Date(Date.parse(stringRepresentingDate)) | |
}, | |
d: 4 | |
} | |
); | |
} | |
); | |
it("ignores empty objects", function () { | |
expect( | |
DateStringsInsideObjectsIntoDates.convert({}) | |
).toEqual({}); | |
}); | |
} | |
); | |
} | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
app.factory('DateStringsInsideObjectsIntoDates', [ | |
'StringToDate', | |
function (StringToDate) { | |
function convert(input) { | |
if (!_.isObject(input)) { | |
return input; | |
} | |
_.each( | |
_.keys(input), | |
function (key) { | |
var value = input[key]; | |
if (_.isString(value)) { | |
input[key] = StringToDate.convert(value); | |
} else { | |
convert(value); | |
} | |
} | |
); | |
return input; | |
} | |
return { | |
convert: convert | |
}; | |
} | |
]); | |
app.factory('StringToDate', [ | |
function () { | |
function convertDateStringToDate(str) { | |
var regexIso8601 = /^(\d{4}|\+\d{6})(?:-(\d{2})(?:-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})\.(\d{1,})(Z|([\-+])(\d{2}):(\d{2}))?)?)?)?$/, | |
match = str.match(regexIso8601), | |
milliseconds; | |
if (!match) { | |
return str; | |
} | |
milliseconds = Date.parse(match[0]) | |
if (!isNaN(milliseconds)) { | |
return new Date(milliseconds); | |
} | |
return str; | |
} | |
return { | |
convert: convertDateStringToDate | |
}; | |
} | |
]); |
Sunday, February 1, 2015
Kata: LCD Digits in Clojure
I've just done the LCD Digits kata in Clojure.
In this kata you have to create an LCD string representation of an integer value using a 3x3 grid of space, underscore, and pipe characters for each digit.
Each digit is shown below (using a dot instead of a space)
and this is the resulting code:
I used a mix of TDD and REPL-driven development to code it.
To document the process I commited the code after every passing test and every refactoring and also commited the REPL history.
You can find the commits step by step here and the code in this repository in GitHub.
In this kata you have to create an LCD string representation of an integer value using a 3x3 grid of space, underscore, and pipe characters for each digit.
Each digit is shown below (using a dot instead of a space)
._. ... ._. ._. ... ._. ._. ._. ._. ._. |.| ..| ._| ._| |_| |_. |_. ..| |_| |_| |_| ..| |_. ._| ..| ._| |_| ..| |_| ..| Example: 910 ._. ... ._. |_| ..| |.| ..| ..| |_|These are the tests using Midje:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns lcd-digits.core-test | |
(:use midje.sweet) | |
(:use [lcd-digits.core])) | |
(facts | |
"LCD digits for numbers" | |
(facts | |
"with one digit" | |
(fact | |
"1 is displayed" | |
(display 1) => (str " \n" | |
" |\n" | |
" |\n")) | |
(fact | |
"7 is displayed" | |
(display 7) => (str " _ \n" | |
" |\n" | |
" |\n")) | |
(fact | |
"4 is displayed" | |
(display 4) => (str " \n" | |
"|_|\n" | |
" |\n")) | |
(fact | |
"0 is displayed" | |
(display 0) => (str " _ \n" | |
"| |\n" | |
"|_|\n")) | |
(fact | |
"5 is displayed" | |
(display 5) => (str " _ \n" | |
"|_ \n" | |
" _|\n")) | |
(fact | |
"2 is displayed" | |
(display 2) => (str " _ \n" | |
" _|\n" | |
"|_ \n")) | |
(fact | |
"9 is displayed" | |
(display 9) => (str " _ \n" | |
"|_|\n" | |
" |\n")) | |
(fact | |
"6 is displayed" | |
(display 6) => (str " _ \n" | |
"|_ \n" | |
"|_|\n")) | |
(fact | |
"3 is displayed" | |
(display 3) => (str " _ \n" | |
" _|\n" | |
" _|\n")) | |
(fact | |
"8 is displayed" | |
(display 8) => (str " _ \n" | |
"|_|\n" | |
"|_|\n"))) | |
(facts | |
"with several digits" | |
(display 18) => (str " _ \n" | |
" | |_|\n" | |
" | |_|\n") | |
(display 910) => (str " _ _ \n" | |
"|_| | | |\n" | |
" | | |_|\n"))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns lcd-digits.core | |
(:require [clojure.string :refer [join]])) | |
(defn- digits-in [n] | |
(map #(Integer/parseInt (str %)) (str n))) | |
(def ^:private fns-by-row | |
{:first (fn [n] | |
(if (or (= n 1) (= n 4)) | |
" " | |
" _ ")) | |
:second (fn [n] | |
(cond | |
(or (= n 8) (= n 9) (= n 4)) "|_|" | |
(= n 0) "| |" | |
(or (= n 5) (= n 6)) "|_ " | |
(or (= n 2) (= n 3)) " _|" | |
:else " |")) | |
:third (fn [n] | |
(cond | |
(or (= n 8) (= n 0) (= n 6)) "|_|" | |
(or (= n 5) (= n 3)) " _|" | |
(= n 2) "|_ " | |
:else " |"))}) | |
(defn- row [row-num n] | |
(str | |
(join | |
" " | |
(map (fns-by-row row-num) (digits-in n))) | |
"\n")) | |
(defn display [n] | |
(str | |
(row :first n) | |
(row :second n) | |
(row :third n))) |
To document the process I commited the code after every passing test and every refactoring and also commited the REPL history.
You can find the commits step by step here and the code in this repository in GitHub.
Subscribe to:
Posts (Atom)