Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

multiple-value-bind and cond

39 views
Skip to first unread message

bradb

unread,
Sep 15, 2006, 6:14:20 PM9/15/06
to
I'm using CL-PPCRE (thanks a lot Edi!) to parse a string multiple times
looking for a match. I may need to parse the same input string against
a couple of different patterns in order to classify the input string.
I also want to know where that match ends. I really want to do
something like:

(cond
((scan pattern1 input) (values 'pattern1 end))
((scan pattern2 input) (values 'pattern2 end)))

Normally you would obtain end by doing:
(multiple-value-bind (start end) (scan pattern1 input)) if pattern1 is
not matched, then start and end will be NIL.
I can't think of any nice way to code this, you either need to
(multiple-value-bind (start end) (scan pattern1 input)
(when start (return (values 'pattern1 end))))
(multiple-value-bind (start end) (scan pattern2 input)
(when start (return (values 'pattern2 end))))

Which is a bit ugly and imperative.

or do the scan twice as
(cond
((scan pattern1 input) (multiple-value-bind (start end) (scan pattern1
input) (values 'pattern1 end))))
Which is just plain ugly.

Any thoughts on a nice way to structure this kind of problem?

Cheers
Brad

Pascal Bourguignon

unread,
Sep 15, 2006, 6:45:22 PM9/15/06
to
"bradb" <brad.be...@gmail.com> writes:

Two solutions:

1- define a macro.

(exercise left to the reader), or


2- use loops.

(loop :named scan
:for pattern :in (list pattern1 pattern2 ...)
:do (multiple-value-bind (start end) (scan pattern input)
(when start (return-from scan (values pattern end)))))

--
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
__Pascal Bourguignon__ http://www.informatimago.com/

Bill Atkins

unread,
Sep 15, 2006, 6:47:14 PM9/15/06
to
"bradb" <brad.be...@gmail.com> writes:

Hmm:

;; acond = anaphoric cond
(acond
((nth-value 1 (scan pattern1 input)) (values 'pattern1 it))
((nth-value 1 (scan pattern2 input)) (values 'pattern2 it)))

Or:

(labels ((match-end (pattern)
(nth-value 1 (scan pattern input))))
(acond ((match-end pattern1) (values 'pattern1 it))
((match-end pattern2) (values 'pattern2 it))))

Or if there are to be a whole bunch of these clauses:

;; only a macro so that you don't have to type "patternx" more than once
(macrolet ((end-of-match pattern)
(let ((result (gensym)))
`(let ((,result `(nth-value 1 (scan ,pattern input))))
(and ,result (list ',pattern ,result)))))
(or (end-of-match pattern1) (end-of-match pattern2)))

HTH,
Bill

bradb

unread,
Sep 15, 2006, 7:00:16 PM9/15/06
to

Thanks for the insight guys. I think I will probably go with Pascal's
loop solution - both code forms look about as pretty/ugly as each
other, and writing a loop means I don't have to write a macro.
Of course, my particular case is a bit lucky because all my cases are
of similar form - otherwise the anaphoric cond would be a good idea.
Assuming for a moment though that I were to write an ACOND style macro,
is it possible to let the anaphor be multiple values? Ie, (IT is the
anaphor)

(ACOND
((scan pattern1 input) (nth-value 1 it)))

Cheers
Brad

Bill Atkins

unread,
Sep 15, 2006, 7:16:22 PM9/15/06
to
"bradb" <brad.be...@gmail.com> writes:

You could include something vaguely this in the expansion of each
clause of your ACOND:

;; untested!
`(... (symbol-macrolet ((it (funcall (lambda () ,form))))
,consequent))

It seems to me that's the only way this could work, since you lose
every value but the first when you assign a multi-valued expression to
IT.

CL-USER> (defun foo ()
(values 0 1 2))
FOO
CL-USER> (symbol-macrolet ((it (funcall (lambda () (foo)))))
(nth-value 1 it))
1

HTH,
Bill

Bill Atkins

unread,
Sep 15, 2006, 7:24:57 PM9/15/06
to
Bill Atkins <atk...@rpi.edu> writes:

(defmacro multiple-value-acond (&rest clauses)
(if (null clauses)
nil
(destructuring-bind (test &rest consequents) (first clauses)

`(symbol-macrolet ((it (funcall (lambda () ,test))))
(if it
(progn
,@consequents)
(multiple-value-acond ,(rest clauses)))))))

(multiple-value-acond
((round 34.5)
(nth-value 1 it)))
;; ==> 0.5

bradb

unread,
Sep 15, 2006, 7:44:35 PM9/15/06
to

Of course 'IT' can potentially get evaluated multiple times. I looked
at the CLHS to see how multiple-value-call/bind works, and I suspect if
somebody was cleverer than me, they could possibly make the underlying
machinery work - but it would be a hell of a hairy macro.
I'm definately going with the LOOP solution, but the anaphoric cond is
an interesting diversion - though I guess all anaphors would have
problems when multiple values come into the picture.

Cheers
Brad

Bill Atkins

unread,
Sep 15, 2006, 8:14:39 PM9/15/06
to
"bradb" <brad.be...@gmail.com> writes:

> Of course 'IT' can potentially get evaluated multiple times. I looked

Oops! Good point:

(defmacro multiple-value-acond (&rest clauses)
(if (null clauses)
nil
(destructuring-bind (test &rest consequents) (first clauses)

(if (null consequents)
test
(let ((it-sym (gensym)) (it-sym-suppliedp (gensym)))
`(let (,it-sym ,it-sym-suppliedp)


(symbol-macrolet
((it (funcall (lambda ()

(if ,it-sym-suppliedp
,it-sym
(setf ,it-sym ,test
,it-sym-suppliedp t))))))
(if it
(progn
,@consequents)
(multiple-value-acond ,@(rest clauses))))))))))
CL-USER> (multiple-value-acond
((prog1 (round 34.5) (princ 'me-printed))
(nth-value 1 it)))
ME-PRINTED
NIL
CL-USER>

Pascal Bourguignon

unread,
Sep 15, 2006, 8:47:51 PM9/15/06
to
Bill Atkins <atk...@rpi.edu> writes:


(defmacro acond (&rest clauses)


(if (null clauses)
nil
(destructuring-bind (test &rest consequents) (first clauses)
(if (null consequents)
test

(let ((them-sym (gensym)) (them-sym-suppliedp (gensym)))
`(let (,them-sym ,them-sym-suppliedp)
(symbol-macrolet
((them
((lambda ()
(if ,them-sym-suppliedp
,them-sym
(setf ,them-sym-suppliedp t
,them-sym (multiple-value-list ,test))))))
(it (first them)))
(if it
(progn ,@consequents)
(acond ,@(rest clauses))))))))))


(acond
((multiple-value-prog1 (round 34.5) (princ 'me-printed))
(print it) (print them)))
ME-PRINTED
34
(34 0.5)
--> (34 0.5)

--
__Pascal Bourguignon__ http://www.informatimago.com/
Until real software engineering is developed, the next best practice
is to develop with a dynamic system that has extreme late binding in
all aspects. The first system to really do this in an important way
is Lisp. -- Alan Kay

bradb

unread,
Sep 15, 2006, 9:09:23 PM9/15/06
to
Oh wow! Thanks for that. I think I've got another few years of Lisp
practice before I can get to that level of macrology, but I really like
that!

Cheers
Brad

Bill Atkins

unread,
Sep 15, 2006, 9:12:35 PM9/15/06
to
Pascal Bourguignon wrote:
>
> (acond
> ((multiple-value-prog1 (round 34.5) (princ 'me-printed))
> (print it) (print them)))
> ME-PRINTED
> 34
> (34 0.5)
> --> (34 0.5)

Nice!

Alan Crowe

unread,
Sep 16, 2006, 11:49:01 AM9/16/06
to

I think this is the wrong place for an anaphor. In general
you want to capture all of the values returned by your
predicate, so you want a binding form. Here is a
multiple-value-cond which is a binding form. You supply
names for variables to capture the multiple values returned
by the guards of the cond clauses and their bodies are
executed inside those bindings.


(defmacro multiple-value-cond (variables &body clauses)
(reduce (lambda(clause nest)
`(multiple-value-bind ,variables ,(first clause)
(if ,(first variables)
,(if (rest clause)
`(progn ,@(rest clause))
`(values ,@variables))
,nest)))
clauses
:from-end t
:initial-value (cons 'values
;; return same number of values
;; as predicate only clause
(make-list (length variables)))))

Example use:


CL-USER> (defun scan (char string)
"We need a predicate that returns multiple
useful values to make the example go"
(let ((first (position char string)))
(if first
(values first
(or (position char
string
:start first
:test-not #'eql)
(length string)))
nil)))
SCAN

CL-USER> (defun scan2 (main-char reserve-char string)
(multiple-value-cond (start end)
((scan main-char string))
((scan reserve-char string)
(format t "Falling back on ~C" reserve-char)
(values start end))))
SCAN2

CL-USER> (scan2 #\a #\b "bbbaaa")
3
6

CL-USER> (scan2 #\a #\b "bbb")
Falling back on b

0
3

CL-USER> (scan2 #\a #\b "ccc")
NIL
NIL

Alan Crowe
Edinburgh
Scotland

Bill Atkins

unread,
Sep 16, 2006, 1:40:42 PM9/16/06
to
Alan Crowe <al...@cawtech.freeserve.co.uk> writes:

Well, I disagree. I think the advantage of the original M-V-C is that
you can use it with forms that return differing numbers of values. I
don't think it makes any sense to specify up front what values you're
expecting, because it's likely that not all of your forms will be in
that format (and if they are, why not just make a label that calls
that function and returns the appropriate value?). For example:

(atkins/bourguignon-multiple-value-acond
((scan input pattern1) (cons (second them) 'foo))
((= (length input) 4) 'no-such-pattern)
(t 'empty-pattern))

How do the second two clauses fit into the code you gave? I prefer
the flexibility of IT/THEM to assuming that all clauses will return
the same number of clauses with the same corresponding meanings. I
think your M-V-C more closely resembles a CASE than a COND, since it's
expecting that the values being considered will have more or less the
same meaning.

Bill

Alan Crowe

unread,
Sep 16, 2006, 5:39:51 PM9/16/06
to
Bill Atkins <atk...@rpi.edu> writes:

> Alan Crowe <al...@cawtech.freeserve.co.uk> writes:
> > (defmacro multiple-value-cond (variables &body clauses)
> > (reduce (lambda(clause nest)
> > `(multiple-value-bind ,variables ,(first clause)
> > (if ,(first variables)
> > ,(if (rest clause)
> > `(progn ,@(rest clause))
> > `(values ,@variables))
> > ,nest)))
> > clauses
> > :from-end t
> > :initial-value (cons 'values
> > ;; return same number of values
> > ;; as predicate only clause
> > (make-list (length variables)))))
>

> Well, I disagree. I think the advantage of the original M-V-C is that
> you can use it with forms that return differing numbers of values. I
> don't think it makes any sense to specify up front what values you're
> expecting, because it's likely that not all of your forms will be in
> that format (and if they are, why not just make a label that calls
> that function and returns the appropriate value?). For example:
>
> (atkins/bourguignon-multiple-value-acond
> ((scan input pattern1) (cons (second them) 'foo))
> ((= (length input) 4) 'no-such-pattern)
> (t 'empty-pattern))
>
> How do the second two clauses fit into the code you gave? I prefer
> the flexibility of IT/THEM to assuming that all clauses will return
> the same number of clauses with the same corresponding meanings. I
> think your M-V-C more closely resembles a CASE than a COND, since it's
> expecting that the values being considered will have more or less the
> same meaning.

My multiple-value-cond inherits from multiple-value-bind the
property of not minding whether the value-form returns the
"correct" number of values.

From the spec

Values-form is evaluated, and each of the vars is bound
to the respective value returned by that form. If there
are more vars than values returned, extra values of nil
are given to the remaining vars. If there are more
values than vars, the excess values are discarded.

On the other hand, my multiple-value-cond does rather
suggest that all the values being considered will have more
or less the same meaning; that is where their mnemonic names
are supposed to come from. If they don't then my macro is
not really doing its job of making the code clearer.

Also if the names are not always used, I need an ignorable
declaration to suppress spurious warnings.

`(multiple-value-bind ,variables ,(first clause)
add ---> (declare (ignorable ,@variables))
(if ,(first variables)

or perhaps atkins/bourguignon-multiple-value-acond instead.

Alan Crowe
Edinburgh
Scotland


WJ

unread,
Mar 2, 2011, 3:36:03 PM3/2/11
to
Pascal Bourguignon wrote:

LOOP is for COBOL programmers.

Guile Scheme:

(define input "a foo bar")

(any (lambda (pat)(string-match pat input))
'("junk" "stuff" "fo+ b" "[a-z]"))


==> #("a foo bar" (2 . 7))

Raymond Wiker

unread,
Mar 2, 2011, 4:08:14 PM3/2/11
to
"WJ" <w_a_...@yahoo.com> writes:

(defun scan (pattern text)
(multiple-value-bind (match-start match-end reg-begins reg-ends)
(cl-ppcre:scan pattern text)
(when match-start
(list text (cons match-start match-end)))))

(some (lambda (pattern)
(scan pattern "a foo bar"))
(list "junk" "stuff" "fo+ b" "[a-z]"))

=> ("a foo bar" (2 . 7))

--- this is possibly not a good idea, as the regular expressions will
have to be re-compiled on each use... but that can be solved by using
cl-ppcre:create-scanner.

WJ

unread,
Mar 2, 2011, 10:10:03 PM3/2/11
to
WJ wrote:

If you need the pattern that matched:


(any (lambda (pat)(let ((m (string-match pat input)))
(if m (list pat m) #f)))


'("junk" "stuff" "fo+ b" "[a-z]"))

==> ("fo+ b" #("a foo bar" (2 . 7)))

rwiker

unread,
Mar 3, 2011, 1:53:06 AM3/3/11
to
Another reason this is not a good idea: all patterns will be evaluated, and then the first match returned, while the loop alternative simply returns as soon as the first match is found.

Soooooo, LOOP may be for COBOL programmers, but also for people more interested in efficiency than WJ appears to be.

Marco Antoniotti

unread,
Mar 3, 2011, 4:06:35 AM3/3/11
to
On Mar 3, 7:53 am, rwiker <rwi...@gmail.com> wrote:
> Another reason this is not a good idea: all patterns will be evaluated, and then the first match returned, while the loop alternative simply returns as soon as the first match is found.
>
> Soooooo, LOOP may be for COBOL programmers, but also for people more interested in efficiency than WJ appears to be.

WJ? Efficiency? It takes him/her/it 5 years to respond to a
thread.... :)

Cheers
--
MA

TheFlyingDutchman

unread,
Mar 3, 2011, 10:36:46 AM3/3/11
to
LOL
0 new messages