Suggest fcase variant "pred-case"

104 views
Skip to first unread message

Stephen C. Gilardi

unread,
Oct 27, 2008, 10:45:22 PM10/27/08
to Clojure
I used Stuart Sierra's 'fcase' contrib today to good effect. Nice job, Stuart!

I have an idea for another fcase variant that I think is useful:

(defmacro pred-case
  "Case where test values are predicates to be applied to the test value."
  [test-value & clauses]
  `(fcase (fn [pred# value#] (pred# value#)) ~test-value ~@clauses))

Here's an example:

user> (defn classify [x] (pred-case x keyword? :keyword string? :string number? :number))
#=(var user/classify)
user> (classify 3)
:number
user> (classify :boo)
:keyword

Stuart, would you please consider adding pred-case to clojure.contrib.fcase?

This also made me wonder if it would be generally useful to have a name for this function:

(fn [f & args] (apply f args))

Do we already? It's not quite apply. It seems a traditional name would be "funcall", but "invoke" sounds more Clojurey. Any thoughts?

Having "invoke" would allow the classify example to be written this way:

(defn classify [x] (fcase invoke x keyword? :keyword string? :string number? :number))

--Steve

Rich Hickey

unread,
Oct 28, 2008, 9:35:21 AM10/28/08
to Clojure


On Oct 27, 10:45 pm, "Stephen C. Gilardi" <squee...@mac.com> wrote:
> I used Stuart Sierra's 'fcase' contrib today to good effect. Nice job,
> Stuart!
>

Yes, I haven't done enough poking around in contrib - fcase looks like
a promising candidate for boot.clj.

> I have an idea for another fcase variant that I think is useful:
>

My thoughts are that there should only be fcase (discussion on name
follows). The variants (re-case, instance-case etc) just pollute the
namespace and add complexity without any new semantics, nor any
significant concision. For the same reasons, we don't have specialized
versions of filter etc. I'd much rather people understand the
semantics of fcase and use it with any predicate they choose:

(fcase instance? ...

(fcase = ...

(fcase re-find ...

> (defmacro pred-case
> "Case where test values are predicates to be applied to the test
> value."
> [test-value & clauses]
> `(fcase (fn [pred# value#] (pred# value#)) ~test-value ~@clauses))
>

That could be:

(fcase #(% %2) ...

> Stuart, would you please consider adding pred-case to
> clojure.contrib.fcase?
>

If you'd like, I think the effort would be better spent on making a
version of fcase for the base library.

The issues I have are with the name, arg ordering and default/no-match
handling.

While fcase is structurally similar to case, case is normally reserved
for equality testing against constant, unevaluated expressions, the
idea being the compiler could possibly leverage knowledge of the
constant values to do something fast. They often have order of test
promises, but I think that's wrong. I'd like to save unadorned "case"
for this meaning.

This doesn't rule out the use of "case" somewhere in the name, but is
something to consider. Another way to look at fcase is like a
parameterized cond.

There is implicit arg ordering (pred clause-value test-value) which
doesn't matter for some preds (=) but does for others (instance?). For
the uses thus far, this order seems good - instance? and re-find work
well, but there will be cases where people will want (pred test-value
clause-value). The order of things in the macro structure might imply
the latter:

(fcase pred test-value
clause-value ...

and it might be difficult for people to remember which it is.

One possibility is to make it explicit in the macro syntax with a
placeholder (_):

(fcase = x _ ;doesn't matter
...

(fcase instance? _ x
...

(fcase some #{x} _
...

or maybe:

(condp (= x _) ;doesn't matter
...

(condp (instance? _ x)
...

(condp (some #{x} _)
...

As far as the default, providing a default value as odd last clause is
ok, but subtle, and could use layout help, if we do that we should do
the same for cond. When no default is provided, there should be some
option for an exception on no match. That could either always be the
case, or a second variant of the macro (e.g. CL has ecase)

Finally there is the name, which has to follow the form and function.
If we go with something like the last above, I like condp/econdp.

I haven't finished my tea, so consider this idle, groggy speculation.

Comment/discussion welcome.

Rich

Stuart Sierra

unread,
Oct 28, 2008, 9:56:59 AM10/28/08
to Clojure
Hi Stephen, Rich,
I haven't had my tea yet either, so I'll look at this some more later.
-S

Stuart Sierra

unread,
Oct 28, 2008, 10:29:49 AM10/28/08
to Clojure
On Oct 27, 10:45 pm, "Stephen C. Gilardi" <squee...@mac.com> wrote:
> I used Stuart Sierra's 'fcase' contrib today to good effect. Nice job,
> Stuart!

Thanks!

On Oct 28, 9:35 am, Rich Hickey <richhic...@gmail.com> wrote:
> If you'd like, I think the effort would be better spent on making a
> version of fcase for the base library.

Cool!

> I'd like to save unadorned "case" for this meaning.

Fine by me.

> There is implicit arg ordering (pred clause-value test-value) which
> doesn't matter for some preds (=) but does for others (instance?). For
> the uses thus far, this order seems good - instance? and re-find work
> well, but there will be cases where people will want (pred test-value
> clause-value). The order of things in the macro structure might imply
> the latter:
...
> (condp (= x _)         ;doesn't matter
> (condp (instance? _ x)
> (condp (some #{x} _)

How about just one argument, the predicate: (condp #(= x %) ...)
That would resolve the issue without adding syntax, no?

> As far as the default, providing a default value as odd last clause is
> ok, but subtle, and could use layout help, if we do that we should do
> the same for cond.

I stole that idea from Arc's "if", which has the interesting quality
of unifying "if" and "cond". I often use the "else" clause to throw
an exception:

(condp #(= x %)
42 "The Meaning of Life"
13 "Unlucky"
1 "The loneliest number"
(throw (Exception. "Nothing matched."))

I feel like "econdp" would be less clear, since you don't know what
kind of exception should be thrown. Plain old Exception? Or
RuntimeError?

> Finally there is the name, which has to follow the form and function.
> If we go with something like the last above, I like condp/econdp.

I'm not sure about condp, only because Clojure hasn't used the CL-ish
"-p" suffix anywhere else. Ideas: condfn, fcond, cond-apply, cond-
with, try-each,...
-S

Martin DeMello

unread,
Oct 28, 2008, 4:13:43 PM10/28/08
to Clojure
On Oct 28, 6:35 am, Rich Hickey <richhic...@gmail.com> wrote:
>
> My thoughts are that there should only be fcase

PLT Scheme's pattern matching library might be a good source of
inspiration:

http://download.plt-scheme.org/doc/352/html/mzlib/mzlib-Z-H-31.html#node_chap_31


martin

Rich Hickey

unread,
Oct 28, 2008, 5:49:25 PM10/28/08
to Clojure


On Oct 28, 10:29 am, Stuart Sierra <the.stuart.sie...@gmail.com>
wrote:
> On Oct 27, 10:45 pm, "Stephen C. Gilardi" <squee...@mac.com> wrote:
>
> > I used Stuart Sierra's 'fcase' contrib today to good effect. Nice job,
> > Stuart!
>
> Thanks!
>
> On Oct 28, 9:35 am, Rich Hickey <richhic...@gmail.com> wrote:
>
> > If you'd like, I think the effort would be better spent on making a
> > version of fcase for the base library.
>
> Cool!
>
> > I'd like to save unadorned "case" for this meaning.
>
> Fine by me.
>
>
>
> > There is implicit arg ordering (pred clause-value test-value) which
> > doesn't matter for some preds (=) but does for others (instance?). For
> > the uses thus far, this order seems good - instance? and re-find work
> > well, but there will be cases where people will want (pred test-value
> > clause-value). The order of things in the macro structure might imply
> > the latter:
> ...
> > (condp (= x _) ;doesn't matter
> > (condp (instance? _ x)
> > (condp (some #{x} _)
>
> How about just one argument, the predicate: (condp #(= x %) ...)
> That would resolve the issue without adding syntax, no?
>

Well, condp *is* syntax, being a macro. This wouldn't add anything to
the reader syntax or anything, just an interpretation of (pred expr _)
or (pred _ expr). #(pred expr %) would cause expr to be evaluated
every clause.

> > As far as the default, providing a default value as odd last clause is
> > ok, but subtle, and could use layout help, if we do that we should do
> > the same for cond.
>
> I stole that idea from Arc's "if", which has the interesting quality
> of unifying "if" and "cond". I often use the "else" clause to throw
> an exception:
>
> (condp #(= x %)
> 42 "The Meaning of Life"
> 13 "Unlucky"
> 1 "The loneliest number"
> (throw (Exception. "Nothing matched."))
>
> I feel like "econdp" would be less clear, since you don't know what
> kind of exception should be thrown. Plain old Exception? Or
> RuntimeError?

The kind of error is TBD, but it won't be random. It saves everyone a)
redundantly having to specify this, and b) specifying needlessly
different Exceptions and messages on no matching case. It's pretty
useful in CL.

>
> > Finally there is the name, which has to follow the form and function.
> > If we go with something like the last above, I like condp/econdp.
>
> I'm not sure about condp, only because Clojure hasn't used the CL-ish
> "-p" suffix anywhere else. Ideas: condfn, fcond, cond-apply, cond-
> with, try-each,...

This wouldn't match the use of trailing p in CL, which has been
replaced by ? in Clojure. I like leading with cond so it sorts
together to make people aware of the option. I don't think I like any
of those better - whatever it is should be short.

Rich

Mike Hinchey

unread,
Oct 28, 2008, 6:21:19 PM10/28/08
to clo...@googlegroups.com

I also like it leading with cond, so condp or condf, but econdp
doesn't fit, so condpe? Having a general naming convention like
trailing F would be nice, I've needed that type of thing before -
assuming this can be generalized. How about :error as the last clause
(test with no expr, or optional expression) instead of a different
macro? Though :error could be a conflict if that was really one of
the tests, so maybe define clojure/error? I disliked looking up so
many different macros for this in CL. Also, would you want an error
variant or option for cond?

-Mike

Rich Hickey

unread,
Oct 28, 2008, 8:03:50 PM10/28/08
to Clojure
> http://download.plt-scheme.org/doc/352/html/mzlib/mzlib-Z-H-31.html#n...
>

Yes, I'm aware of that one, but it's more of a structural matcher than
this predicate thing we've been discussing. I expect some sort of
structural match facility is inevitable in Clojure, and something like
the PLT one, plus some leverage of Clojure's data structure literals,
would probably be nice.

Rich
Reply all
Reply to author
Forward
0 new messages