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.