Another bite

We're only going to look at one more function from the Containers core protocol, flat-map. I'll leave the others until I can introduce some more data types to illustrate their use.

The flat-map protocol function is probably the single most useful of all the core protocol functions. What does it do? Glad you asked.

Suppose we wanted to see if the first element of a list of numbers was equal to 15. First, we need to get the first element of the list

  (first (list 15 99 24))
  => <maybe 15>

(The => is just short hand for 'produces:'. Toccata doesn't have a REPL that you can type these expressions into yet. Look in the examples directory for some short programs to run.)

So first returns a Maybe value that has the first element in it. So far, the way we've seen to reach inside a Maybe value is with the map function.

  (map (first (list 15 99 24))
       (fn [x]
         (= 15 x)))
  => <maybe <maybe 15>>

That's not right. What we need is a function similar to map that will apply a given function to the value inside a Maybe value and then remove one layer of wrapping (or 'flatten') the result. flat-map to the rescue.

  (flat-map (first (list 15 99 24))
            (fn [x]
              (= 15 x)))
  => <maybe 15>

But what happens when the list is empty and first returns a nothing value? Exactly what you'd expect.

  (flat-map (first (list))
            (fn [x]
              (= 15 x)))
  => nothing

And finally, what if the head of the list isn't 15?

  (flat-map (first (list 1 2 3))
            (fn [x]
              (= 15 x)))
  => nothing

And now, with lists!

How does flat-map work with lists? Pretty straightforward. First regular map.

  (map (list 15 99 24)
       (fn [x]
         (list (inc x))))
  => ((16), (100), (25))

Nothing too surprising, I hope. And now flat-map

  (flat-map (list 15 99 24)
            (fn [x]
              (list (inc x))))
  => (16, 100, 25)

And for completeness, flat-map on an empty list

  (flat-map (list)
            (fn [x]
              (list (inc x))))
  => ()

also, if the result of the given function is an empty list

  (flat-map (list 1 2 3)
            (fn [x]
              (list)))
  => ()

for!

If you're a Clojure programmer, you might have thought about Clojure's for expression for sequence comprehension.

  (for [x (list 15 99 24)]
    (inc x))
  => (16, 100, 25)

Toccata has the exact same expression. It is another example of a special form that could be a macro. The above expression gets expanded to

  (let [#arg (list 15 99 24)]
    (flat-map #arg (fn [#x]
                     (wrap #arg (inc #x)))))

So for is written in terms of wrap and flat-map. But that also means that, in Toccata for works for any data type that implements those function. Like Maybe

  (for [head (first (list 15 99 24))]
    (inc head))
  => <maybe 16>

This is a huge thing in Toccata and we'll eventually spend a lot more time looking at this.