case?

Started by Sammo, December 21, 2003, 03:34:35 PM

Previous topic - Next topic

Sammo

It would be nice in 8.0 or later for the un-evaluated 'exp-n' values in 'case' be allowed to have muliple values in, say, the form of lists.  For example,


(case (lower-case name)
    (("bob" "mary" "susie") 'friend)
    (("tim" "diane") 'boss)
    ("sam" 'self)
    ("sammo" 'clever-alias)
    (true 'stranger))
Perhaps there is a macro or a work-around that offers multiple case targets.

nigelbrown

#1
This is a bit round about but uses a lambda-macro to accept conditions as a string of conditions that can have multiple matches defined.

The functions expandit converts ((1 2 3) 4) into ((1 4) (2 4) (3 4))

or (3 4) into ((3 4)). Expander goes throgh a list of such conditions and expands them with expandit and appends them together. Mycase is a lambda macro  that uses expander to make a case statement that is then eval'ed.

So

> (mycase "diane" ( (("bob" "mary" "susie") 'friend) (("tim" "diane") 'boss) ("sam" 'self) ("sammo" 'clever-alias) (true 'stranger) ))

boss

>

As macro don't evaluate the list of conditions is an unquoted list.

I don't think newlisp handles indeterminate length parameter lists eg like lisp does with &rest keyword so conditions must be lumped into one list.



the functions are:

(define-macro (mycase _x _c)  (eval (append (list 'case _x) (expander _c))))



(define (expandit x) (if (list? (first x)) (map (lambda (y) (append (list y) (rest x))) (first x)) (list x)))

 

(define (expander c , z) (begin (setq z '()) (dolist (xx c) (setq z (append z (expandit xx)))) z))



All run in one place:

newLISP v7.4.0 Copyright (c) 2004 Lutz Mueller. All rights reserved.



> (define (expander c , z) (begin (setq z '()) (dolist (xx c) (setq z (append z (expandit xx)))) z))

(lambda (c , z)

 (begin

  (setq z '())

  (dolist (xx c)

   (setq z (append z (expandit xx)))) z))

> (define (expandit x) (if (list? (first x)) (map (lambda (y) (append (list y) (rest x))) (first x)) (list x)))

(lambda (x)

 (if (list? (first x))

  (map (lambda (y) (append (list y) (rest x))) (first x))

  (list x)))

> (define-macro (mycase _x _c)  (eval (append (list 'case _x) (expander _c))))

(lambda-macro (_x _c) (eval (append (list 'case _x) (expander _c))))

> (mycase "diane" ( (("bob" "mary" "susie") 'friend) (("tim" "diane") 'boss) ("sam" 'self) ("sammo" 'clever-alias) (true 'stranger) ))

boss

> (mycase 5 (((1 3 5 7 9) "odd") ((2 4 6 8) "even")))

"odd"

> (mycase 6 (((1 3 5 7 9) "odd") ((2 4 6 8) "even")))

"even"

>



This may be a starting point for a simpler solution (or a red herring).

Nigel

nigelbrown

#2
re my comment:

I don't think newlisp handles indeterminate length parameter lists eg like lisp does with &rest keyword so conditions must be lumped into one list.



I've found the args fn for define-macro - I'll try recasting the functions using this.

Nigel

nigelbrown

#3
Here is the improved mycase using (args):

(define-macro (mycase2 _x) (eval (append (list 'case _x) (expander (rest (args))))))

other functions as before.

Now looks more like case viz:

> (mycase2 "diane" (("bob" "mary" "susie") 'friend) (("tim" "diane") 'boss) ("sam" 'self) ("sammo" 'clever-alias) (true 'stranger))

boss

>

and

> (mycase2 3 ((1 3 5 7 9) "odd") (10 "ten") ((2 4 6 8) "even") (true "none"))

"odd"

> (mycase2 10 ((1 3 5 7 9) "odd") (10 "ten") ((2 4 6 8) "even") (true "none"))

"ten"

> (mycase2 2 ((1 3 5 7 9) "odd") (10 "ten") ((2 4 6 8) "even") (true "none"))

"even"

> (mycase2 35 ((1 3 5 7 9) "odd") (10 "ten") ((2 4 6 8) "even") (true "none"))

"none"

>



Hope this helps

Nigel

HPW

#4
Nigel,



nice function.

Do you allow to add it to the sample.lsp/init.lsp file of the neobook/newlisp interface?

Would be a nice example on newLISP!
Hans-Peter

nigelbrown

#5
Yes, feel free. However, the definition of expander seems a bit ham fisted - now that args is in the picture there may be a better way of iterating through the conditions that need expansion - but I can't see it.

I tried (map but it puts the expanded conditions one level too deep in the list. Use it as it is but "Improvements are left as an exercise for the student".

Thanks for your kind comment.

Regards

Nigel

Lutz

#6
nice idea the 'expander' funtionality, but a comment to Sam's suggestion to expand the case syntax:



>>> Sam suggestion

(case (lower-case name)

    (("bob" "mary" "susie") 'friend)

     ....

    (true 'stranger))

>>>>



When newLISP would compare 'name' it would compare with each expression, including lists, so imagine 'name' is the list: '("bob" "mary" "susie"), so there is no way to handle 'case' with the old syntax but multiple constants for one case-level.



The only thing possible would be to have only the last element in the list be the returned-evaluation:



(case name

("bob" "mary" "susie" body-expression)  

....



But at this moment all functions in newLISP which take body expressions for evaluation, like "case", "dotimes", "dolist", "cond", "while" etc. can take multiple body expressions and the last one is the return value:



(case name

("bob" body-1 body-2 body-3) ; evaluation of body-3 gets returned

....



this saves (begin ... ) blocks and would have to be given up to allow multiple switch constants.



We could have a new function 'translate':



(translate name '((bob mary susie friend) (tim diane boss)) 'stranger)



Basically an 'assoc' with variable length sublists and an optional default return. To make this thing small and fast, nothing inside the association list would be evaluated, it would be all constants. In a way this would be a cousin of 'lookup'. 'lookup' has multiple return values in the asssociation, while 'translate' has multiple key values in the association.



Lutz

nigelbrown

#7
Hello

1)RE:

When newLISP would compare 'name' it would compare with each expression, including lists, so imagine 'name' is the list: '("bob" "mary" "susie"), so there is no way to handle 'case' with the old syntax but multiple constants for one case-level.



- true the suggested case variant - and mycase macros - will not allow a list as a (first of a condition



2)RE:

(case name

("bob" body-1 body-2 body-3) ; evaluation of body-3 gets returned

....



this saves (begin ... ) blocks and would have to be given up to allow multiple switch constants.



I don't see that it is given up. The expandit approach of mycase just pastes the (rest onto expanded list items thus:



with:

> (define (body1) (print "B1"))

(lambda () (print "B1"))

> (define (body2) (print "B2"))

(lambda () (print "B2"))

> (define (body3) (print "B3"))

(lambda () (print "B3"))



usage is:

> (case 2 (1 (body2)) (2 (body1) (body2) (body3)))

B1B2B3"B3"

>

similarly:

> (mycase2 2 (1 (body2)) ((2 3 4) (body1) (body2) (body3)))

B1B2B3"B3"

>



Regards

Nigel

Lutz

#8
yes, I agree, I was talking about the original post how it was trying to expand the original case syntax, not your 'expandit' macro.



Lutz

nigelbrown

#9
How about this for a suggested case variant along sammo's lines



(case x (match1? target1 body1) (match2? target2 body2 body3))

Where quoted x - so it can be ("billy" "bones") or "bill" or 2 - is compared with target using specified function match? , if true do bodies.



I've constructed a mycase4 along these lines viz:

# to make a list of length n of repeated quoted elements x

(define (makelistq x n , z) (begin (setq z '()) (for (i 1 n) (setq z (append (list (list 'quote x )) z))) z))



# macro to construct a (cond element that will act as above

(define-macro (makecondeval _x _y) (list (append (list (first (eval _y))) (list (eval _x)) (list (first (rest (eval _y))))) (append (list 'begin ) (rest (rest (eval _y))))))



# macro to string things together to construct and eval a full (cond

# the print is to show what is happening

(define-macro (mycase4 _x) (eval (print (append (list 'cond) (map makecondeval (makelistq (first (args))  (length (rest (args)))) (rest (args)))))))



example of use is:

> (mycase4 "gus" (member '("jak" "sam") 'boss) (= '("billy" "bones") 'friend) (member '("erol" "gus") 'foe))

(cond

 ((member (quote "gus") '("jak" "sam"))

  (begin

   'boss))

 ((= (quote "gus") '("billy" "bones"))

  (begin

   'friend))

 ((member (quote "gus") '("erol" "gus"))

  (begin

   'foe)))foe

Note targets should be quoted if not want evaluation.

for multiple comparisons use member

for list compared to list etc use =

construct any predicate you like for full control



without printing it is:

(define-macro (mycase4 _x) (eval (append (list 'cond) (map makecondeval (makelistq (first (args))  (length (rest (args)))) (rest (args))))))



Below is a captured output with some examples.

A bit of a long post but explaining was hard.

Regards

Nigel





Working all in one place:

newLISP v7.4.0 Copyright (c) 2004 Lutz Mueller. All rights reserved.



> (define (makelistq x n , z) (begin (setq z '()) (for (i 1 n) (setq z (append (list (list 'quote x )) z))) z))

(lambda (x n , z)

 (begin

  (setq z '())

  (for (i 1 n)

   (setq z (append (list (list 'quote x)) z))) z))

> (define-macro (makecondeval _x _y) (list (append (list (first (eval _y))) (list (eval _x)) (list (first (rest (eval _y))))) (append (list 'begin ) (rest (rest (eval _y))))))



(lambda-macro (_x _y) (list (append (list (first (eval _y))) (list

    (eval _x))

   (list (first (rest (eval _y)))))

  (append (list 'begin) (rest (rest (eval _y))))))

> (define-macro (mycase4 _x) (eval (append (list 'cond) (map makecondeval (makelistq (first (args))  (length (rest (args)))) (rest (args))))))

(lambda-macro (_x) (eval (append (list 'cond) (map makecondeval (

     makelistq

     (first (args))

     (length (rest (args))))

    (rest (args))))))

> (mycase4 "gus" (member '("jak" "sam") 'boss) (= '("billy" "bones") 'friend) (member '("erol" "gus") 'foe))

foe

> (mycase4 ("billy" "bones") (member '("jak" "sam") 'boss) (= '("billy" "bones") 'friend) (member '("erol" "gus") 'foe))

friend

> (mycase4 2 (member '("jak" "sam") 'boss) (= '("billy" "bones") 'friend) (member '("erol" "gus") 'foe))

nil

> (mycase4 5 (member '(2 4 6 8) "even") (member '(3 5 7) "odd"))

"odd"

> (define (fn1) (print "fn1"))

(lambda () (print "fn1"))

> (define (fn2) (print "fn2"))

(lambda () (print "fn2"))

> (mycase4 5 (member '(2 4 6 8) (fn1) (fn2)) (member '(3 5 7) (fn2) (fn1)))

fn2fn1"fn1"

> (mycase4 6 (member '(2 4 6 8) (fn1) (fn2)) (member '(3 5 7) (fn2) (fn1)))

fn1fn2"fn2"

>

Sammo

#10
Nigel,



Thank you very much for the 'mycase2' macro.  It is just what I need.  With your solution, I see no need to change/improve newLISP's 'case' function.



I have taken the liberty of renaming 'mycase2' to 'ncase' because 1) 'n' stands for 'nigel', and 2) 'n' indicates that each clause can have n targets.



I found a lisp'ier solution for 'expander' and added a couple of comments to explain 'expandit'.  Here is my adaptation of your code:


(define-macro (ncase _x)
    (eval (append (list 'case _x) (expander (rest (args))))))

(define (expander c)
    (apply append (map expandit c)))

(define (expandit x)
    (if (list? (first x))
        ;((a b c) d e) --> ((a d e) (b d e) (c d e))
        (map (lambda (y) (append (list y) (rest x))) (first x))
    ;else
        ;(a d e) --> ((a d e))
        (list x) ))
The revised 'expander' code can be rolled into the 'ncase' macro as follows:


(define-macro (ncase _x)
    (eval (append (list 'case _x) (apply append (map expandit (rest (args)))))))

nigelbrown

#11
Hi Sammo

Thanks for fixing expander to make it a lisp function rather than the iterative hack it was before - it never felt right. I don't have proper use of apply in my skills so I'll learn to use it.

Glad you found my thoughts a helpful starting point.



Regards

Nigel