FPIC Archive 3: I am a horse in the land of booleans

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        ;; complient is used for autocompletion
        ;; add your libs here (and restart the runtime to pick up changes)
        compliment/compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation
{:hello (clojure-version)}
0.1s
Clojure

Any program is only as good as it is useful. Linus Torvalds

Fork this

https://github.com/iloveponies/i-am-a-horse-in-the-land-of-booleans

Here are the instructions if you need them. Be sure to fork the repository behind the link above.

If then else

Any non-trivial program needs conditionals. Clojure’s if looks like the following:

(if (my-father? darth-vader) ; Conditional (lose-hand me) ; If true (gain-hat me)) ; If false

if (usually) takes three parameters: the conditional clause, the then body and the else body. If the first parameter - the conditional clause - is true, the then body is evaluated. Otherwise, the else body is evaluated.

Clojure has two boolean values: true and false. However, all values can be used in a boolean context like if. Everything except nil and false acts as true. For example, all of the following are valid Clojure:

(if "foo" "truthy" "falsey") ;=> "truthy" (if 0 "truthy" "falsey") ;=> "truthy" (if [] "truthy" "falsey") ;=> "truthy" (if false "truthy" "falsey") ;=> "falsey" (if nil "truthy" "falsey") ;=> "falsey"

nil is Clojure’s null value. We’ll talk about it later.

To make it easier to talk about values in boolean context, we define the following terminology:

  • If a value is considered true in boolean context, we call it truthy.

  • If a value is considered false, we call it falsey.

Any value can be turned into true or false with the boolean function:

(boolean "foo") ;=> true (boolean nil) ;=> false

Exercise 1

Implement (boolean x), which works like the built-in boolean function: for nil and false, it returns false, and for all other values it returns true. You can use if in its implementation, but not the build-in boolean.

(boolean "foo") ;=> true (boolean nil) ;=> false (boolean (+ 2 3)) ;=> true (boolean true) ;=> true (boolean false) ;=> false

Comparing values

Values can be compared for equality with =:

(= "foo" "foo") ;=> true (= "foo" "bar") ;=> false

Numerical values should be compared with ==:

(== 42 42) ;=> true (== 5.0 5) ;=> true (= 5.0 5) ;=> false !

Note the difference between = and ==== disregards the actual type of the numeric value, whereas = requires that the numbers are of the same type.

Less-than, greater-than and other such comparisons can be done with the regular <><= and >= operators:

(< 1 2) ;=> true (> 1 2) ;=> false (<= 52 2) ;=> false

Comparing many values

All the comparison functions above take an arbitrary amount of arguments. For an example, to compare if three variables are equal, one can give them to =:

(= x y z) ;=> true if and only if x = y = z

The other comparison operators work similarly. You can easily check if given variables are in ascending order:

(< x y z q) ;=> true if and only if x < y < z < q

Exercise 2

Write the function (teen? age), which returns truthy if age is at least 13 and at most 19. Use only one comparison operator and give it three arguments.

(teen? 12) ;=> false (teen? 13) ;=> true (teen? 15) ;=> true (teen? 19) ;=> true (teen? 20) ;=> false (teen? 27) ;=> false

Everything has a value

In functional programming, and specifically in Clojure, everything is an expression. This is a way of saying that everything has a usable value. Concretely, if has a return value; the value is the value of the evaluated body (either the then or the else body).

As an example, let’s define the function (sign x), which returns the string "-" if x is negative and otherwise "+". The function looks like the following:

(defn sign [x] (if (< x 0) "-" "+"))

The function definition has one expression, which is an if expression. The value of the function is the value of the if expression, because it is the last expression in the function body. The value of the if expression is either "-" or "+", depending on the value of the parameter x.

You can paste the function into the REPL and try calling it with various values of x:

(sign 2) ;=> "+" (sign -2) ;=> "-" (sign 0) ;=> "+"

There is no need for a return clause – there is no such keyword in Clojure – because the return value of a function is always the value of the last expression in the body of the function.

Watch out!

if does not have a return value in a language like Java. In other words, it is not an expression, but a statement. Because everything in Clojure is an expression, there is no equivalent construct to Java’s if in it.

For illustration, you could use Java’s if to implement sign:

String sign(int x) { if (x < 0) return "-"; else return "+"; }

Note that you need to use the return keyword to indicate when to return from the method. Compare this to Clojure, where the last expression’s value will be the function’s return value. Because Java’s if does not return a value, you can not say:

return if (x < 0) "-" else "+"; // Illegal Java!

Conditional evaluation

In any case, only the appropriate expression is evaluated. So the following is not an error:

(if true 42 (/ 1 0))

If evaluated, (/ 1 0) would throw an ArithmeticException due to the division by zero. However, the if expression does not evaluate the division at all, because the conditional clause is true and only the then body, 42, is evaluated.

Exercise 3

Write the function (abs n), which returns the absolute value of n, i.e. if \(n < 0\), the value of (abs n) is \(- n\), and otherwise \(n\).

(abs -2) ;=> 2 (abs 42) ;=> 42

Exercise 4

Write the function (divides? divisor n), which returns true if divisor divides n and false otherwise.

(mod num div) returns 0 if div divides num exactly:

(mod 10 5) ;=> 0 (mod 3 2) ;=> 1

(divides? 2 4) ;=> true (divides? 4 2) ;=> false (divides? 5 10) ;=> true (divides? 2 5) ;=> false

Conditioning

When checking for multiple conditions, you can use multiple if clauses:

(if condition1 true1 (if condition2 true2 (if condition3 true3 ...)))

This is similar to if/else if in languages like Java. However, the nested if clauses are awkward. We can rewrite the nested if clauses with the cond builtin.

The general form of cond is:

(cond condition1 true1 condition2 true2 condition3 true3 ...)

Like with if, you can have an else branch in the end. The condition for the else branch is :else.

(defn super-sign [number] (cond (neg? number) "negative" (pos? number) "positive" :else "zero")) (super-sign 13) ;=> "positive" (super-sign 0) ;=> "zero" (super-sign -5) ;=> "negative"

Exercise 5

Write the function (fizzbuzz n) that returns

  • "fizz" when n is divisible by 3,

  • "buzz" when n is divisible by 5,

  • "gotcha!" when n is divisible by 15, and

  • the empty string "" otherwise.

Use the divides? function from the previous exercise.

(fizzbuzz 2) ;=> "" (fizzbuzz 45) ;=> "gotcha!" (fizzbuzz 48) ;=> "fizz" (fizzbuzz 70) ;=> "buzz"

Exercise 6

Write a function (generic-doublificate x) that takes one argument and

  • doubles it if it is a number,

  • returns nil if it is an empty collection,

  • if it is a list or a vector, returns two times the length of it

  • returns true otherwise.

You can use the following functions:

  • (number? n) returns true if n is a number.

  • (empty? coll) returns true if coll is empty.

  • (list? coll) and (vector? coll) test if coll is a list or a vector.

  • (count coll) returns the length of a list or a vector.

(generic-doublificate 1) ;=> 2 (generic-doublificate [1 2]) ;=> 4 (generic-doublificate '(65 21)) ;=> 4 (generic-doublificate {}) ;=> nil (generic-doublificate []) ;=> nil (generic-doublificate {:a 1}) ;=> true

Boolean Functions

The common boolean functions in Clojure are andor and not. These roughly match the &&|| and ! operators of languages like Java.

(and true true) ;=> true (and true false) ;=> false (or true false) ;=> true (or false false) ;=> false (not true) ;=> false (not false) ;=> true

and and or take an arbitrary amount of arguments:

(and true) ;=> true (and true true true) ;=> true (and true true true true false) ;=> false (and) ;=> true (or false false false false true) ;=> true (or false false false) ;=> false (or) ;=> nil

In addition to booleans, andor and not accept non-boolean values as arguments as well. (Remember that false and nil are falsey and everything else is truthy.)

By the way, if you have a lot of long parameters to and, or any function for that matter, indent them like this:

(and very-long-boolean-holding-parameter another-one-that-is-a-friend-of-the-previous the-third-guy)

and returns truthy if all of its arguments are truthy:

(and "foo" "bar") ;=> "bar" (and "foo" false) ;=> false (and 10 nil) ;=> nil

or returns truthy if any of its arguments is truthy:

(or "foo" false) ;=> "foo" (or 42 true) ;=> 42

not returns true if its argument is falsey and false if its argument is truthy:

(not "foo") ;=> false (not nil) ;=> true (not []) ;=> false

This behaviour might look surprising, but it is consistent. What’s happening is that if all the arguments to and are truthy, it returns the value of the last argument. Otherwise, it returns the value of the first falsey argument. Conversely, or returns either the first truthy value or the last falsey value.

While it might seem odd that boolean functions return non-boolean values, remember that all values in Clojure in fact act as boolean values. This behaviour is useful in many situations. For an example, it allows you to provide default values for variables when taking input:

(def server-port (or user-input 80))

Note that this can only be used in situations where the input may not take the values false or nil.

Exercise 7

Write the function (not-teen? age), which returns true when teen? returns false and false otherwise.

(not-teen? 13) ;=> false (not-teen? 25) ;=> true (not-teen? 12) ;=> true (not-teen? 19) ;=> false (not-teen? 20) ;=> true

Exercise 8

Write the function (leap-year? year), which returns true if year is a leap year, otherwise false. A year is a leap year if it is divisible by 4, except if it is divisible by 100, in which case it still is a leap year if it is divisible by 400.

See Wikipedia for a simple pseudocode solution.

(leap-year? 100) ;=> false (leap-year? 200) ;=> false (leap-year? 400) ;=> true (leap-year? 12) ;=> true (leap-year? 20) ;=> true (leap-year? 15) ;=> false

Proceed to structuration! →

Runtimes (1)