Abstract structural binding

19 views
Skip to first unread message

Rich Hickey

unread,
Feb 4, 2008, 5:07:57 PM2/4/08
to Clojure
Jealous of pattern matching - no more! (Unless you want a structural
switch statement, in which case I can't help you :)

I've added a prototype for abstract structural binding (as of SVN rev
646)

It's built on a destructuring let, temporarily called let*.

When passed only symbols as binding forms, as does normal let, let*
behaves exactly as let, i.e. it is a direct replacement.

In addition to taking symbols as binding forms, let* can also take
vectors and maps.

The vectors allow you to bind names to parts of _sequential_ things
(not just vectors!), like vectors, lists, seqs, strings, arrays, and
anything that supports seq.

The basic sequential form is a vector of symbols, which will be bound
to successive elements from the binding value. In addition, and
optionally, & followed by a symbol will cause that symbol to be bound
to the 'rest' of the sequence, i.e. that part not yet bound. Finally,
also optional, :as followed by a symbol will cause that symbol to be
bound to the entire binding value:

(let* [[a b c & d :as e] [1 2 3 4 5 6 7]] [a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]

These forms can be nested:

(let* [[[x1 y1][x2 y2]] [[1 2] [3 4]]] [x1 y1 x2 y2])
->[1 2 3 4]

Strings too:

(let* [[a b & c :as str] "asdjhhfdas"] [a b c str])
->[\a \s (\d \j \h \h \f \d \a \s) "asdjhhfdas"]

Map binding forms allow you to bind names to parts of _associative_
things (not just maps!), like maps, vectors, string and arrays (the
latter three have integer keys). It consists of a map of symbol-key
pairs, each symbol being bound to the value in the binding value at
the key. In addition, and optionally, an :as key in the binding map
followed by a symbol will cause that symbol to be bound to the entire
binding value. Also optionally, an :or key in the binding map followed
by another map may be used to supply default values for some or all of
the keys if they are not found in the binding value:

(let* [{a :a, b :b, c :c, :as m :or {a 2 b 3}} {:a 5 :c 6}]
[a b c m])
->[5 3 6 {:c 6, :a 5}]

The new binding forms can be nested within one another arbitrarily,
allowing you to pull apart just about anything:

(let* [a 1
[b c [d e & f :as x] {h :h} & g] [2 3 [4 5 6] {:h 100 :foo 2}
7 8 9]
{j :j, k :k, i :i, [r s & t] :ivec :as m :or {i 12 j 13}} {:j
15 :k 16 :ivec [22 23 24 25]}]
[a b c d e f g h i j k r s t m x])

-> [1 2 3 4 5 (6) (7 8 9) 100 12 15 16 22 23 (24 25) {:ivec [22 23 24
25], :j 15, :k 16} [4 5 6]]

I've switched 'for' to expand to let*, so now it, too, can take vector
and map binding forms:

(for [[a b c] (map list (range 1 10) (range 11 20) (range 21 30))] [c
b a])
-> ([21 11 1] [22 12 2] [23 13 3] [24 14 4] [25 15 5] [26 16 6] [27 17
7] [28 18 8] [29 19 9])

In addition I've provided fn*, defn*, and defmacro* which let you do
the same thing with fn parameters:

(defn* foo
([a [b c] d] [a b c d])
([a [b c] d & r] [a b c d r]))

(foo 1 [2 3] 4 5 6 7)
->[1 2 3 4 (5 6 7)]

All of these ___* names are temporary. My inclination is to have the
destructuring versions replace their counterparts, but I want to get
some more experience and some feedback first.

If you are at all interested, please try this out.

Feedback welcome as always,

Rich

Henk Boom

unread,
Feb 4, 2008, 5:44:25 PM2/4/08
to clo...@googlegroups.com
On 04/02/2008, Rich Hickey <richh...@gmail.com> wrote:
>
> Jealous of pattern matching - no more! (Unless you want a structural
> switch statement, in which case I can't help you :)
>

Noooooo, I was just working on a pattern matcher myself, as an
exercise for learning lisp-style macros.

Seriously though, this is awesome. Are there any plans for
restrictions in the patterns, to constrain certain sub-patterns with
literals/values or predicates?

> All of these ___* names are temporary. My inclination is to have the
> destructuring versions replace their counterparts, but I want to get
> some more experience and some feedback first.

That would blow my mind. I'm currently learning haskell, and one thing
which strikes me about it is how useful the built-in pattern matching
is (except I've felt a need for the :as feature you have). As it's
compile time, this shouldn't impact the runtime performance of
functions using the regular old case-lambda or single patterns, right?

I like the direction Clojure is going. =)

Henk

Rich Hickey

unread,
Feb 4, 2008, 8:47:54 PM2/4/08
to Clojure


On Feb 4, 5:44 pm, "Henk Boom" <lunarcri...@gmail.com> wrote:
> On 04/02/2008, Rich Hickey <richhic...@gmail.com> wrote:
>
>
>
> > Jealous of pattern matching - no more! (Unless you want a structural
> > switch statement, in which case I can't help you :)
>
> Noooooo, I was just working on a pattern matcher myself, as an
> exercise for learning lisp-style macros.
>

You could just not look at the implementation :)

> Seriously though, this is awesome. Are there any plans for
> restrictions in the patterns, to constrain certain sub-patterns with
> literals/values or predicates?
>

I'm not exactly sure what you are looking for here. It is important to
note that this is just a binding construct and not a conditional.

> > All of these ___* names are temporary. My inclination is to have the
> > destructuring versions replace their counterparts, but I want to get
> > some more experience and some feedback first.
>
> That would blow my mind. I'm currently learning haskell, and one thing
> which strikes me about it is how useful the built-in pattern matching
> is (except I've felt a need for the :as feature you have). As it's
> compile time, this shouldn't impact the runtime performance of
> functions using the regular old case-lambda or single patterns, right?
>

Right - if you don't use any structural matching it generates
precisely the same code as let.

Rich

Henk Boom

unread,
Feb 4, 2008, 11:48:10 PM2/4/08
to clo...@googlegroups.com
On 04/02/2008, Rich Hickey <richh...@gmail.com> wrote:
> On Feb 4, 5:44 pm, "Henk Boom" <lunarcri...@gmail.com> wrote:
> > Seriously though, this is awesome. Are there any plans for
> > restrictions in the patterns, to constrain certain sub-patterns with
> > literals/values or predicates?
> >
>
> I'm not exactly sure what you are looking for here. It is important to
> note that this is just a binding construct and not a conditional.
>

Ahh I see now that this does not work:
(defn* foo ([[a]] a) ([[a b]] b))

I understand better now.

Henk

John Cowan

unread,
Feb 5, 2008, 8:53:41 AM2/5/08
to clo...@googlegroups.com
On Feb 4, 2008 11:48 PM, Henk Boom <lunar...@gmail.com> wrote:

> Ahh I see now that this does not work:
> (defn* foo ([[a]] a) ([[a b]] b))

Few, if any, functional programming languages support the multiple
appearance of an identifier on the left side of a let. But if you
want Prolog (which does do unification of this kind), you know where
to find it.

--
GMail doesn't have rotating .sigs, but you can see mine at
http://www.ccil.org/~cowan/signatures

Rich Hickey

unread,
Feb 5, 2008, 9:47:20 AM2/5/08
to Clojure


On Feb 5, 8:53 am, "John Cowan" <johnwco...@gmail.com> wrote:
> On Feb 4, 2008 11:48 PM, Henk Boom <lunarcri...@gmail.com> wrote:
>
> > Ahh I see now that this does not work:
> > (defn* foo ([[a]] a) ([[a b]] b))
>
> Few, if any, functional programming languages support the multiple
> appearance of an identifier on the left side of a let. But if you
> want Prolog (which does do unification of this kind), you know where
> to find it.
>

What he has there are 2 independent fn methods, each taking one
argument, and was looking for structural binding to choose between
them based upon the number of items in the argument when treated as a
sequence, i.e. something akin to ML/Haskell/Erlang pattern matching,
which is not the intent or mechanism of Clojure's structural binding.

Rich

John Cowan

unread,
Feb 5, 2008, 1:02:11 PM2/5/08
to clo...@googlegroups.com
On Feb 5, 2008 9:47 AM, Rich Hickey <richh...@gmail.com> wrote:

> What he has there are 2 independent fn methods, each taking one
> argument, and was looking for structural binding to choose between
> them based upon the number of items in the argument when treated as a
> sequence, i.e. something akin to ML/Haskell/Erlang pattern matching,
> which is not the intent or mechanism of Clojure's structural binding.

Yeah, you're right. My bad.

Reply all
Reply to author
Forward
0 new messages