Introduction to Clojure - Part 2

Tools

Topics

  • Functions on collections
  • High order functions
  • Lazy sequences
  • Loop/Recur

Topics for later

Functions on collections

Collections play a central role in clojure
The collections share a common interface.



In clojure, collections are called sequences.
Usually, sequences are lazy.

Sequences - interface

Here are a couple of functions available on sequences and here is the full interface for sequences

count

(count (zipmap (range 100) (range 200))) 

seq is useful when we need to test whether a sequence is empty

[(seq [1 2 3])
        (seq [])]

We can take a few element - even from an infinite sequence:

(take 10 (range))

We can take elements while a predicate holds:

(take-while (partial > 10) (range))

We can also take the last elements of a list - but don't try that on an infinite list!

(take-last 10 (range 100))

Play also with drop drop-last and drop-while...

Sequences - high order functions

map

(map inc [1 2 3])

filter

(filter even? (range 10))

remove

(remove even? (range 10))

reduce

(reduce (fn [res x] (* res x)) 1 (range 1 11))

Map, filter and reduce

Compose a string from the letters whose ascii values are odd numbers

We are going to implement this in several ways, improving our code style at each stage:

  1. The worst way:

    (reduce (fn [res i]
                   (if (odd? i) (str res (char i)) res))
                 "" (range 97 123))

    Why is it bad?

  2. Giving each stage a name:

    (let [lst (range 97 123)
                      f-lst (filter odd? lst)
                      m-lst (map char f-lst)]
                  (reduce str "" m-lst))
  3. Using the as-> threading macro

    (identity (as-> (range 97 123) $
                      (filter odd? $)
                      (map char $)
                      (reduce str "" $)))
  4. Using the ->>threading macro:

    (->> (range 97 123)
                     (filter odd?)
                     (map char)
                     (reduce str ""))

Sequences - Practice

Write by yourslef the following funtions of clojure.core

Advanced Practice

Write a clojure version of spark's function reduceByKey: receives a list of (K, V) pairs and a function f and returns a list of (K, V) pairs where the values for each key are aggregated using the given reduce function f

(defn reduce-by-key [f lst])
        (and 
          (= (reduce-by-key '((a 1)) +) '((a 1)))
          (= (reduce-by-key '((a 1) (a 3)) +) '((a 4)))
          (= (reduce-by-key '((a 1) (a 3) (b 1) (a 5) (b 6)) +) '((a 9) (b 7))))

High order functions

Function that receives function[s] and return a function

High order functions

apply

(apply + (range 10))

comp

((comp list inc) 9)

You can call apply on comp. For instance:

((apply comp [inc inc inc]) 3)

partial

(def match-aa (partial re-matches #"aa"))
        [(match-aa "aaaa")
        (match-aa "aa")]

High order functions (cont.)

memoize

First, a non-memoized function that print its arguments

When called twice with the same argument, it prints twice

(defn foo[x] (println "foo called with: " x))
        (symbol (with-out-str
          (foo 1)
          (foo 1)))

With memoization, the function is called only once, the other calls are cached:

(def foo-memo (memoize foo))
        (with-out-str 
          (foo-memo 1)
          (foo-memo 1))

juxt

((juxt inc dec (partial * 10) even?) 1)

High Order Functions - Practice

Write by yourslef the following funtions of clojure.core

comp (only with 2 functions)

partial (only supporting functions with 2 arguments)

comp with no limit on the number of functions

partial supporting functions any number of arguments

Lazy Sequences

Elements are evaluated only when we really need them



The sequences returned by a lot of clojure functions are lazy: map, filter, concat, range, take...

Lazy Sequences - Examples

Infinite range of numbers:

(take 10 (range))

Infinite repetition of an element:

(take 100 (repeat 1))

Infinite repetition of a sequence:

(take 100 (cycle [1 2 3]))

Infinite repetition of a function call:

(take 10 (repeatedly (partial rand-int 100)))

Lazy Sequences - creation

There is a handy macro lazy-seq that makes it easy to create lazy sequences. Let's see it in action, by writing code that receives a sequece and increment each of its elements - without using map:

First a non-lazy implementation - with a infinite recursion

(defn inc-seq [s]
                (cons (inc (first s))
                      (inc-seq (rest s))))
            (take 10 (inc-seq (range 100)))

Let's fix our code - by ending the recursion when the list is empty

(defn inc-seq [s]
              (when (seq s)
                (cons (inc (first s))
                      (inc-seq (rest s)))))
            (take 10 (inc-seq (range 100)))

And now the lazy implementation:

(defn inc-seq-lazy [s]
              (lazy-seq
                (cons (inc (first s))
                      (inc-seq-lazy (rest s)))))
            (take 10 (inc-seq-lazy (range 100)))

We can even pass an infinite lazy sequence to our function:

(take 10 (inc-seq-lazy (range)))

Lazy sequences - Practice

Write a function that creates a infinite lazy sequence of the positive integers

First write a function numbers-starting-at that receives a number n and returns all the numbers starting at n

(defn numbers-starting-at [n] )
            (= (take 3 (numbers-starting-at 5)) '(5 6 7))

Now write a function numbers that receive no arguments and return all the positive integers. Try to use partial

Tail Call Recursion

Regular recursions are dangerous as they might cause stack overflow. In clojure, there is a semantic support for tail call recursion.



At first, it's less intuitive...
But when you get used to it, it's very convenient...

Tail Call Recursion - Basics

Tail Call Recursion - Practice

Congratulations


Now, you are familiar with:

/