In your example (and in David's) the implementation of the slice function is locked into the definition of that function. The real power of protocols comes when you use them to decouple this.
Here's an example (in Clojure because I don't know the names of the types Clojurescript uses off the top of my head, but the ideas are the same):
(defprotocol Sliceable
(slice [this start end]))
(extend-protocol Sliceable
clojure.lang.PersistentVector
(slice [this start end]
(subvec this start end)))
(extend-protocol Sliceable
java.lang.String
(slice [this start end]
(.substring this start end)))
(slice "Hello" 0 2)
(slice [:a :b :c] 0 2)
Here we've defined a protocol called "Sliceable" and added support for it to two built-in classes. We've done so in a safe way -- the "slice" function isn't visible anywhere outside this namespace unless you import it.Now say you come along and want to use my little "slicing" library to carve up someone else's data structures. You can add support without touching my files or their files:
(ns foo
(:use fancylists [:only (FancyList)])
(:use stevelosh.slicing [:only (Sliceable)]))
(extend-protocol Sliceable
FancyList
(slice [this start end]
(... slice up the fancylist here ...)))
And now in another file you want to slice up a FancyList: (ns bar
(:use fancylists [:only (FancyList)])
(:use stevelosh.slicing [:only (slice)]))
(def my-fancy-list (FancyList 1 2 "cat" "dog"))
(slice my-fancy-list 2 4)
; => (a FancyList containing "cat" and "dog")
Now you've added support for some random person's FancyList library to my Slicing library, without touching the code in either of them. This is the really cool part about Protocols.This concept actually does appear in David's post when he adds support for his Slice type to the IFn protocol, but it's a bit obscured by the fact that function calling has some syntactic sugar so you don't have to write -invoke.