proposal: match multimethod

22 views
Skip to first unread message

Stuart Halloway

unread,
Dec 1, 2008, 5:07:20 PM12/1/08
to clo...@googlegroups.com
I am thinking about adding a match method to Clojure-contrib. This
would work like Ruby's threequals ("===", a.k.a. case equality) and
would be implemented as a multimethod to do sensible things with a
wide variety of types.

(1) Good idea?

(2) What should it be named?

Stuart

Rich Hickey

unread,
Dec 2, 2008, 11:22:17 AM12/2/08
to Clojure
For those of us that don't do Ruby - what does === do?

Rich

Stuart Halloway

unread,
Dec 2, 2008, 12:16:41 PM12/2/08
to clo...@googlegroups.com
It is called "case equality" which is a terribly confusing way to say
"the predicate used to match in case statements". "Match" is really
the best verb. In Ruby, most things match by value equality. But
classes match their instances. Ranges match things in the range.
Regexps match strings that they would match against.

I find the construct useful, but difficult to define. In particular,
if regular expressions match against matching strings, should
collections match against their members? Subsets?

The use case I have in mind for Clojure is in unit tests, where one
might say something like

(each-matches
[actual-value expected-value]+)

and have the match operator applied.

Stuart

Rich Hickey

unread,
Dec 2, 2008, 2:08:28 PM12/2/08
to Clojure


On Dec 2, 12:16 pm, Stuart Halloway <stuart.hallo...@gmail.com> wrote:
> It is called "case equality" which is a terribly confusing way to say
> "the predicate used to match in case statements". "Match" is really
> the best verb. In Ruby, most things match by value equality. But
> classes match their instances. Ranges match things in the range.
> Regexps match strings that they would match against.
>
> I find the construct useful, but difficult to define. In particular,
> if regular expressions match against matching strings, should
> collections match against their members? Subsets?
>
> The use case I have in mind for Clojure is in unit tests, where one
> might say something like
>
> (each-matches
> [actual-value expected-value]+)
>
> and have the match operator applied.
>

I'm pretty sure I don't like the sound of that at all. We had a nice
discussion about fcase/condf, which I'd like to get in, here:

http://groups.google.com/group/clojure/browse_frm/thread/dee910bef6296035/d1858b3b0233183e

I think a predicate match system is much nicer than a fuzzy-value
match.

Also, I think people will want a true structural match macro at some
point (yesterday).

Rich

Martin DeMello

unread,
Dec 2, 2008, 11:46:55 PM12/2/08
to Clojure
On Dec 2, 9:22 pm, Rich Hickey <richhic...@gmail.com> wrote:
>
> For those of us that don't do Ruby - what does === do?

It's a supporting method for the case keyword

case object
when pred1: action1
when pred2: action2
..
end

translates to

if pred1 === object
action1
elsif pred2 === object
action2
..
end

It's not really a fuzzy-value match, just nice syntactic sugar for a
multiply-dispatched match method.

martin

axo...@gmail.com

unread,
Dec 3, 2008, 11:37:41 AM12/3/08
to Clojure


On Dec 2, 2:08 pm, Rich Hickey <richhic...@gmail.com> wrote:
> On Dec 2, 12:16 pm, Stuart Halloway <stuart.hallo...@gmail.com> wrote:
> > In Ruby, most things match by value equality. But
> > classes match their instances. Ranges match things in the range.
> > Regexps match strings that they would match against.

> fcase/condf, which I'd like to get in, here:
>
> http://groups.google.com/group/clojure/browse_frm/thread/dee910bef629...

> Also, I think people will want a true structural match macro at some
> point (yesterday).

Here is a wide-open design space of facilities with overlapping
functionality that can be put to a wide variety of different
purposes. cond clauses, case statements, generic procedure
dispatch a la CLOS, pattern-directed invocation a la ML and
Haskell, destructuring function arguments into formal parameters,
destructuring data structures by patterns, matching regular
expressions, and even pattern matching for operating a rule-based
deduction system all consist of two basic components: which
pattern successfully matches determines the code to be executed
next, and the bindings to the variables of that pattern are
handed to that next piece of code. They vary in how much each
aspect is emphasized and in the language of permissible patterns,
and in the user-extensibility of the pattern language and the
match sites. I would love for someone to work out a coherent
Grand Unified Theory of Pattern Matching. Failing that my two
cents are to suggest that the design of constructs in this space,
like fcase or ===, would benefit from conciously carving out an
appropriate chunk of the space for them to inhabit.

~Alexey

Stuart Sierra

unread,
Dec 3, 2008, 1:00:44 PM12/3/08
to Clojure
On Dec 2, 2:08 pm, Rich Hickey <richhic...@gmail.com> wrote:
> I'm pretty sure I don't like the sound of that at all. We had a nice
> discussion about fcase/condf, which I'd like to get in, here:
>
> http://groups.google.com/group/clojure/browse_frm/thread/dee910bef629...

And I haven't forgotten about that, just haven't had time to work on
it. Anyone else who wants to tackle it is welcome.
-Stuart Sierra

Meikel Brandmeyer

unread,
Dec 3, 2008, 2:51:46 PM12/3/08
to clo...@googlegroups.com
Hi Stuart and Rich,

Am 03.12.2008 um 19:00 schrieb Stuart Sierra:
>> I'm pretty sure I don't like the sound of that at all. We had a nice
>> discussion about fcase/condf, which I'd like to get in, here:
>>
>> http://groups.google.com/group/clojure/browse_frm/thread/
>> dee910bef629...
>
> And I haven't forgotten about that, just haven't had time to work on
> it. Anyone else who wants to tackle it is welcome.

How about the following:

(defmacro condp
"condp compares the given needle with the first clause using the
given
predicate. In case the predicate returns true the second clause is
returned. Otherwise condp goes on with the rest of the clauses. In
case there is an odd number of clauses the last one will be returned
if no preceding clause matched. If no default clause is given an
exception is thrown. The predicate is called with needle as first
argument and the first clause as second argument."
[pred needle & clauses]
(let [c (count clauses)
par (rem c 2)
cls (take (if (zero? par) c (dec c)) clauses)
cls (mapcat (fn [[x c]] `[(~xprd ~xndl ~x) ~c]) (partition 2
cls))
xndl (gensym "condp_needle__")
xprd (gensym "condp_predicate__")
lst (if (zero? par)
`(throw (Exception. (str "No condp clause matched for: "
(prn-str ~xndl))))
(last clauses))]
`(let [~xprd ~pred
~xndl ~needle]
(cond
~@cls
:else ~lst))))

I mulled about the (x _) syntax, but to be honest: I think #() is
perfectly sufficient. We would need to quote all the contents to
prevent multiple evaluation, but then we have to recurse and check
for _ vs. non-_...

I expect the predicate to be a function of two arguments with the first
being the needle and the second being the clause, we compare to.
Predicates where order doesn't matter or the order agrees may be simply
used. Others or more complicated expressions can be wrapped in a #().
Here we still have a problem for #(pred (complicated-computation) %2
%1)....

In case the number of clauses is even, and no clause matched the
predicate, we throw an exception. In case the number of clauses is
odd, we return the last one as default. I think adding a simple nil
is tolerable, making the "I ignore a failed run" explicit.

What do you think?

Sincerely
Meikel

Brian Doyle

unread,
Dec 3, 2008, 3:10:19 PM12/3/08
to clo...@googlegroups.com
Can you include an example usage of this function?  Thanks.

Meikel Brandmeyer

unread,
Dec 3, 2008, 3:10:27 PM12/3/08
to clo...@googlegroups.com
Hello again,

Am 03.12.2008 um 20:51 schrieb Meikel Brandmeyer:
> cls (mapcat (fn [[x c]] `[(~xprd ~xndl ~x) ~c]) (partition 2
> cls))
> xndl (gensym "condp_needle__")
> xprd (gensym "condp_predicate__")

Oops. These should of course be changed to be in the
right order.

Sincerely
Meikel

Meikel Brandmeyer

unread,
Dec 3, 2008, 3:32:02 PM12/3/08
to clo...@googlegroups.com
Hi,

Am 03.12.2008 um 21:10 schrieb Brian Doyle:

> Can you include an example usage of this function? Thanks.

(condp = x
1 "We got a one."
2 (str "We got a " (- 3 1))
"We got something else."))

Sincerely
Meikel

Rich Hickey

unread,
Dec 3, 2008, 8:30:07 PM12/3/08
to Clojure
needle is a strange name - what's the origin? expr is probably better.

I'm ok with odd clause is default, else throw if not match.

I think the expr should be passed second to the fn - in most cases
where it matters, that is a more useful default:

contains?, re-find, instance? etc.

I almost whipped this up yesterday, but I got sidetracked thinking
about how best to provide a feature like Scheme's cond's =>. Do you
know it? It feeds the value of the test to the fn on the rhs of the
clause. It can be useful.

Rich

Meikel Brandmeyer

unread,
Dec 4, 2008, 4:44:10 AM12/4/08
to Clojure
Hello Rich,

On 4 Dez., 02:30, Rich Hickey <richhic...@gmail.com> wrote:
> needle is a strange name - what's the origin? expr is probably better.

I thought of it as needle, which we search in the haystack. But
this comparison is not very good after a second thought. Changed
needle to expr.

> I think the expr should be passed second to the fn - in most cases
> where it matters, that is a more useful default:

Ok. Changed.

> I almost whipped this up yesterday, but I got sidetracked thinking
> about how best to provide a feature like Scheme's cond's =>. Do you
> know it? It feeds the value of the test to the fn on the rhs of the
> clause. It can be useful.

Yes. There were times I wanted that, but the pain wasn't big
enough to make me implement it. I changed cond to expect either
a vector or something else. The idea is to use the usual [var val]
form for the binding.

(cond
[y (some #{:a :b :c} [:x :b :y])] (do-something-with y here)
(normal-predicate form works as before) (y is different-here)
:else works-also)

For condp I attached the binding to the predicate definition.

(condp [y some] [:x :b :y]
#{:a :b :c} (do-something with y)
#{:x :y :z} (same y here))

I think, using return value of the predicate basically depends
on the latter. For "some" it may make sense, for "instance?"
it is not very interesting. So I thought having to type one y is
better than to repeat it in all the clauses.

For cond it's different, because here the tests are independent.
So it makes more sense to specify the binding explicitely.

Of course, this still works:

(condp = x
1 "A one"
2 "A two"
"many")

Since the web-interface doesn't allow attachments I will
upload the patch in the files section to prevent clobbering
of newlines and stuff.

Sincerely
Meikel

Meikel Brandmeyer

unread,
Dec 5, 2008, 12:45:50 PM12/5/08
to clo...@googlegroups.com
Hi,

Am 04.12.2008 um 10:44 schrieb Meikel Brandmeyer:
>> I almost whipped this up yesterday, but I got sidetracked thinking
>> about how best to provide a feature like Scheme's cond's =>. Do you
>> know it? It feeds the value of the test to the fn on the rhs of the
>> clause. It can be useful.

It seems my patch is completely needless: In contrib
there is already cond-let, which does what you want.

Sincerely
Meikel

Meikel Brandmeyer

unread,
Dec 18, 2008, 6:26:42 PM12/18/08
to clo...@googlegroups.com
Hi,

I reworked my initial proposal according to the comments
of Rich. The syntax now looks as follows:

(condp predicate expr
test-expr result-expr
test-expr :> result-expr
...
default-expr)

A result-expr is chosen according to (predicate test-expr expr).
In the first form, result-expr is simply evaluated and the result
is returned. In the second form, result-expr is expected to
evaluate to a function. This function gets passed the return
value of the predicate call. Whatever the function returns
is returned by condp.

If no test-expr leads to a success the default-expr is evaluated
and the result returned. If no default-expr is supplied an
exception is thrown.

The patch is attached at the issue tracker.
http://code.google.com/p/clojure/issues/detail?id=6#c2

Comments appreciated.

Sincerely
Meikel

Mark Volkmann

unread,
Dec 19, 2008, 7:12:59 AM12/19/08
to clo...@googlegroups.com
On Thu, Dec 18, 2008 at 5:26 PM, Meikel Brandmeyer <m...@kotka.de> wrote:
> Hi,
>
> I reworked my initial proposal according to the comments
> of Rich. The syntax now looks as follows:
>
> (condp predicate expr
> test-expr result-expr
> test-expr :> result-expr

What is ":>"? Is that just a keyword whose name is ">"? I think adding
more characters with special meaning makes code harder to read, so I
hope we don't add more of these.

> ...
> default-expr)
>
> A result-expr is chosen according to (predicate test-expr expr).
> In the first form, result-expr is simply evaluated and the result
> is returned. In the second form, result-expr is expected to
> evaluate to a function. This function gets passed the return
> value of the predicate call. Whatever the function returns
> is returned by condp.
>
> If no test-expr leads to a success the default-expr is evaluated
> and the result returned. If no default-expr is supplied an
> exception is thrown.
>
> The patch is attached at the issue tracker.
> http://code.google.com/p/clojure/issues/detail?id=6#c2
>
> Comments appreciated.
>
> Sincerely
> Meikel
>
>

--
R. Mark Volkmann
Object Computing, Inc.

Meikel Brandmeyer

unread,
Dec 19, 2008, 7:21:02 AM12/19/08
to Clojure
Hi Mark,

On 19 Dez., 13:12, "Mark Volkmann" <r.mark.volkm...@gmail.com> wrote:

> What is ":>"? Is that just a keyword whose name is ">"?

It's exactly that: a keyword with the name ">".

> I think adding more characters with special meaning
> makes code harder to read, so I hope we don't add
> more of these.

It comes from (some) Scheme. There you can "pipe"
the result of a test expression into the result expression
using "=>".

(cond
(test-expr => result-expr))

result-expr should actually be a function, which is
called with the result of the test-expr.

If you don't need this, you don't have to use it.

(condp instance? x
String "some string"))

is (more or less) equivalent to

(cond
(instance? String x) "some string"))

Sincerely
Meikel

Reply all
Reply to author
Forward
0 new messages