Booleans

The two boolean values are: "true" and "false". Respectively #t or #true and #f or #false in Guile.

In a conditional test context, "true" means any expression other than #f (or #false).

I invite you to create a new directory in the workspace dedicated to this chapter: ~/Workspace/guile-handbook/booleans.

Write the test first

Create the file booleans-test.scm :

(use-modules (srfi srfi-64)
             (booleans))

(test-begin "harness")

(test-equal "true-inverted-returns-false"
  #f
  (boolean-invert #t))
  
(test-end "harness")

Try and run the test

guile -L . booleans-test.scm

Compilation error !

;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;;       or pass the --no-auto-compile argument to disable.
;;; compiling /home/jeko/Workspace/guile-handbook/booleans/booleans-test.scm
;;; WARNING: compilation of /home/jeko/Workspace/guile-handbook/booleans/booleans-test.scm failed:
;;; no code for module (booleans)

Write the minimal amount of code for the test to run and check the failing test output

Compilation errors can be seen as red tests. In the tradition of TDD, it is essential to add only the bare minimum of code to correct these errors.

Here, the compiler indicates that the booleans module does not exist. I create it to correct this error and immediately restart the test.

Create the file booleans.scm :

(define-module (booleans))

A new error occurs! This time it compiles, but I'm warned that the boolean-invert variable is not linked (i.e. it is not defined). I add it to my previously created module and re-run the test.

Edit the file booleans.scm :

(define-module (booleans))

(define-public (boolean-invert bool)
  0)

No more errors or warnings at compile time. The test fails and you can check the reason for the failure in the harness.log report. A quick look confirms that the reason for the failure is that the true-inverted-returns-false test waits for the value #f while the boolean-invert procedure always returns 0.

$ cat harness.log 
%%%% Starting test harness
Group begin: harness
Test begin:
  test-name: "true-inverted-returns-false"
  source-file: "booleans-test.scm"
  source-line: 6
  source-form: (test-equal "true-inverted-returns-false" #f (boolean-invert #t))
Test end:
  result-kind: fail
  actual-value: 0
  expected-value: #f
Group end: harness
# of unexpected failures  1

Write enough code to make it pass

I modify the boolean-invert procedure so that it returns the value #f as expected by the test.

Edit the file booleans.scm :

(define-module (booleans))

(define-public (boolean-invert bool)
  #f)

Running the test displays the following message :

$ guile -L . booleans-test.scm
;;; note: source file /home/jeko/Workspace/guile-handbook/booleans/booleans-test.scm
;;;       newer than compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/booleans/booleans-test.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;;       or pass the --no-auto-compile argument to disable.
;;; compiling /home/jeko/Workspace/guile-handbook/booleans/booleans-test.scm
;;; compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/booleans/booleans-test.scm.go
%%%% Starting test harness  (Writing full log to "harness.log")
# of expected passes      1

The test passes !

Refactor

There's not much to do for so little code.

Docstrings

I take this opportunity to tell you about docstrings. These strings bring a little explanation to the user if needed:

  • in the REPL, with the command ,describe.
  • in Emacs

These are strings placed in second position in the parameter list when defining a variable or a procedure with define, define* or define-public (and so on).

Let's add a docstring to the boolean-invert procedure!

Edit the file booleans.scm :

(define-module (booleans))

(define-public (boolean-invert bool)
  "Returns the opposite value of the given boolean."
  #f)

Write the test first

The first test verified that calling the boolean-invert procedure with the #t parameter returns #f. The next test will check the reverse.

Edit the file booleans-test.scm :

(use-modules (srfi srfi-64)
             (booleans))

(test-begin "harness")

(test-equal "true-inverted-returns-false"
  #f
  (boolean-invert #t))
 
(test-equal "false-inverted-returns-true"
  #t
  (boolean-invert #f))
  
(test-end "harness")

Try and run the test

$ guile -L . booleans-test.scm 

It compiles without any problem!

Write the minimal amount of code for the test to run and check the failing test output

You can see the new test failling.

booleans-test.scm:10: FAIL false-inverted-returns-true

Check that the reason for the failure is the expect one. That is, the false-inverted-returns-true test waits for the value #t but gets #f.

Test begin:
  test-name: "false-inverted-returns-true"
  source-file: "booleans-test.scm"
  source-line: 10
  source-form: (test-equal "false-inverted-returns-true" #t (boolean-invert #f))
Test end:
  result-kind: fail
  actual-value: #f
  expected-value: #t

This is confirmed.

Write enough code to make it pass

Let's add the minimum amount of code required to pass the test:

(define-module (booleans))

(define-public (boolean-invert bool)
  "Returns the opposite value of the given boolean."
  (if bool
      #f
      #t))

Run the tests again…

%%%% Starting test harness  (Writing full log to "harness.log")
# of expected passes      2

All clear !

Refactor

As you might have guessed, there is a ready-made procedure for inverting a boolean :

(define-module (booleans))

(define-public (boolean-invert bool)
  "Returns the opposite value of the given boolean."
  (not bool))

I restart the tests one last time to check that this re-machining hasn't broken anything:

%%%% Starting test harness  (Writing full log to "harness.log")
# of expected passes      2

Done.

Wrapping up

What has been covered in this chapter :

  • More TDD practice
  • Notions about booleans
  • Write self-documented code thanks to docstrings