Inconsistent syntax of "case"

Started by seetho, January 12, 2009, 07:12:57 PM

Previous topic - Next topic

seetho

I'm going through Cormullion's Introduction to newLISP.  Everything was going down really smoothly.  The syntax was very consistent and simple (in a good way) and everything governed by the 3 simple rules, until I came to the case statement example.  Syntax consistency was what drew me to newLISP in the first place (away from CL and Scheme).



I found that the "exp-switch" part is not evaluated as default.  This really (to a newbie like me) sticks out like a very tiny splinter on an otherwise very smooth piece - if you don't hold it just right it'll prick you!



Shouldn't the exp-switch be evaluated like everything else to keep consistency?  You can always stop the evaluation using quotes.  I think there's a macro that does evaluation, but shouldn't that be the rule instead of the exception?



I may be missing a bigger picture here, so I'd appreaciate some comments from any newLISP gurus here.



Thanks.

Lutz

#1
There are several other built-in functions in newLISP where arguments are not evaluated. In the case of the 'case' function, newLISP does what users from other LISPs would expect. Assuming unevaluated constants make for a faster, efficient and more readable (explicit) case-statement. Use 'cond' or 'if' to conditionally branch on evaluated expressions.

seetho

#2
Hi Lutz,



Now it makes more sense that you are calling it "constants".  It naturally comes to mind that constants should not be evaluated.  Maybe it should be called "const-switch" instead of "exp-switch" in the manual.  Just my humble opinion.



Thanks for clearing it up.

cormullion

#3
Hi seetho - thanks for reading my Intro! :)



There's a fine line between simplifying and over-simplifying - in my attempts to explain things to myself and others I hope I haven't conveyed the impression that there are 'only' three simple rules with no exceptions...



But I think that newLISP is fairly consistent, both with itself, and with other languages. But not 100% consistent in both directions.... That would be a bit extreme. ;)



The subtleties of evaluation is the thread that runs from the very first pages of an introduction right through to the advanced expressions that make my head hurt (and that you won't find in anything I write..!)..

seetho

#4
Hi cormullion,



Your tutorial is a great help in my learning newLISP.  I count programming as one of my hobbies since my first VIC-20 - ok some of you know my age group now ;)



I think that whatever number of rules that govern a programming language, be it 3 or 30, the most important thing is consistency.  (Although I really wish it to be just 3 for newLISP - and Why Not!?)  Simple does not equate to useless.  IMHO the most useful and enduring programming languages are, at the core, very simple.  



Anyway I feel that newLISP has great potential.  I look forward to getting enough stuffed into my head to start coding some useful applications soon.

cormullion

#5
Cool, thanks!



The 3 simple rules thing is of course my idea. No such thing, really - Lisp syntax has to be more complex than that. Take a look at http://www.newlisp.org/ExpressionEvaluation.html">//http://www.newlisp.org/ExpressionEvaluation.html, for example... :)



Even something as simple as an 'if' expression could be seen as an exception - for obvious reasons. Is (exit) evaluated first?


(if (= 5 (+ 2 2)) "ok" (exit))

DrDave

#6
Quote from: "cormullion"Cool, thanks!



Even something as simple as an 'if' expression could be seen as an exception - for obvious reasons. Is (exit) evaluated first?


(if (= 5 (+ 2 2)) "ok" (exit))

But doesn't the question of (exit) evaluation fall under lazy evaluation, hence making the evaluation of 'if' expressions more of a nuance than an actual exception to argument evaluation?
...it is better to first strive for clarity and correctness and to make programs efficient only if really needed.

\"Getting Started with Erlang\"  version 5.6.2

Kazimir Majorinc

#7
IF is traditional example of what cannot be done as function in Common Lisp or Scheme. But, it can be done in Newlisp, because Newlisp - beside static scope in contexts - has very powerful dynamic scope support.


(set 'IF (lambda(condition)
     (let ((z (find (eval condition) '(doesntmatter nil))))
           (eval (nth (dec (inc z)) $args)))))

(let ((x 2)(y 3))
     (IF '(< x y)
         '(println  x " is less than " y)
         '(println  x " is not less than " y))
               
     (IF '(> x y)
         '(println x " is greater than " y)
         '(println x " is not greater than " y)))

; 2 is less than 3
; 2 is not greater than 3
;


Inc dec thingy looks like trick, but it is not really important, the point is that IF understand its arguments, they are not only empty syntax like in mainstream Lisps. So, unlike CL and Scheme, in Newlisp, it is matter of convenience whether some argument is evaluated or not. There was such discussion about inc, i.e (inc x) vs (inc 'x). It is matter of convenience for users, and Lutz treats bulit in primitives on similar way.



In long terms, maybe some syntax colouring could be used in manual to indicate expressions treated on "macro-like" way. For example



(if condition then-part else-part)




But it is not really that simple.
http://kazimirmajorinc.com/\">WWW site; http://kazimirmajorinc.blogspot.com\">blog.

seetho

#8
At first look, the IF statement is really puzzling if you stubbornly insist that everything in a list is evaluated (sequentially), but then if you look again carefully as DrDave mentions it still is consistent.



If you think in terms of everything being processed in parallel then it may easier to imagine.  Take for example that this list/expression if received by a super multi-processor computing environment.  The statement



(IF (= 5 (+ 2 2)) "ok" (exit))



get passed to processor#1 to handle.  Processor#1 then decides that it can pass on



(= 5 (+ 2 2)) to processor#2, "ok" to processor#3 and (exit) to processor#4.



Everything is now evaluated in parallel and processor#1 knows that the IF function indicated that it needs to decide which result to use (either from processor#3 or #4) will depend on what's return by processor#2.  Of course processor#2 further divides up the work to more processors, but eventually it figures out that it should return NIL.



Upon receiving the NIL result from processor#2, processor#1 decides to take the result of processor#3.  The result of processor#4 is discarded.  Even if processor#4 went ahead and evaluated the (exit) expression it is not necessary that it is executed, it merely returns and EXIT signal back to processor#1.  Whether it is used or not and how this signal is ultimately handled is entirely up to processor#1.



So conceptually everything is evaluated.



Did that make sense?

DrDave

#9
Quote from: "seetho"So conceptually everything is evaluated.



Did that make sense?

It made sense to me. In a nutshell, if I understand correctly, you are saying in your multi-processor example that all the arguments indeed are evaluated, but it is up to the 'If function to make a determination of how to proceed after the results of the evaluation of the arguments occurs,  However, I think in newLISP that if the first argument evaluates to true, then  the second argument is never processed (lazy evaluation), thereby saving the computation time, etc., that would be required for that evaluation.
...it is better to first strive for clarity and correctness and to make programs efficient only if really needed.

\"Getting Started with Erlang\"  version 5.6.2

seetho

#10
Quote from: "DrDave"However, I think in newLISP that if the first argument evaluates to true, then  the second argument is never processed (lazy evaluation), thereby saving the computation time, etc., that would be required for that evaluation.


Let's just say the 2nd argument "did not need to be" evaluated instead of "never" because it could have been evaluated if we wanted to.  Just that the results will not be used.  Of course in practice, resource efficiency is important so "lazy evaluation" is adopted to cut down on unneccessary work.  I can imagine that in a multi-threaded enviroment optimised for speed, the 2nd argument would've been evaluated in any case.

Kazimir Majorinc

#11
Quote from: "seetho"(IF (= 5 (+ 2 2)) "ok" (exit))



get passed to processor#1 to handle. Processor#1 then decides that it can pass on



(= 5 (+ 2 2)) to processor#2, "ok" to processor#3 and (exit) to processor#4.



Everything is now evaluated in parallel and processor#1 knows that the IF function indicated that it needs to decide which result to use (either from processor#3 or #4) will depend on what's return by processor#2.



...



So conceptually everything is evaluated.



Did that make sense?


Actually, there is a problem, because in if statetement, it is not only result of the evaluation of then-branch and else-branch that matters, but side effects matter as well. So, in your mental model, it is not enough that processor #1 decides which result it will use (#3 or #4), it is important that evaluation of the "wrong one" never actually happened.



Unfortunately, when you see Newlisp expression (f expr1 expr2 ... ) you cannot know whether expr1 expr2 ... are evaluated if you do not know the value of f. You can define your own IF, CASE etc. with semantics consistent on the way you want (see my IF above) if you really want that. Not that I advice it, especially not at the beginning of using Newisp, but it is possible.







Going back to your original question, in



(case exp-switch (exp-1 body-1) [(exp-2 body-2) ... ])



exp-switch is evaluated, thats why it is possible to write


(set 'n 3)

(println (case n
              (1 "one")
              (2 "two")          
              (3 "three")
              (4 "four")))


but other parts are not evaluated as default, only by demand. That's why you cannot write something like


(set 'n 3)
(set 'm 3)
(println (case n
              (1 "one")
              (2 "two")          
              (m "three"); n=m not recognized
              (4 "four")))
http://kazimirmajorinc.com/\">WWW site; http://kazimirmajorinc.blogspot.com\">blog.

seetho

#12
Quote from: "Kazimir Majorinc"Actually, there is a problem, because in if statetement, it is not only result of the evaluation of then-branch and else-branch that matters, but side effects matter as well. So, in your mental model, it is not enough that processor #1 decides which result it will use (#3 or #4), it is important that evaluation of the "wrong one" never actually happened.


So in the example of an IF statement you'd have to wait for the PREDICATE clause to return before deciding on executing the THEN clause or the ELSE clause.  If I understand you correctly, this is because the PREDICATE clause may have some side effects on either the THEN or ELSE clauses, making it impossible to evaluate them in parallel.  Still either one will get evaluated eventually.


Quote(case exp-switch (exp-1 body-1) [(exp-2 body-2) ... ])


My original gripe was that exp-1 implied that it was evaluated.  It would've been better to call it const-1 so that you get](case exp-switch (const-1 body-1) [(const-2 body-2) ... ])[/code]

You see "const" then you know that you cannot put an expression there. I know it seems a petty matter to those already familiar with the concept, but to newbies like me emerging from decades of languishing in the "dark side" of imperative languages, it does make things easier to grasp.

DrDave

#13
Quote from: "seetho"
Let's just say the 2nd argument "did not need to be" evaluated instead of "never" because it could have been evaluated if we wanted to.  Just that the results will not be used.


I don't think that is correct for newLISP. My understanding is that in the case where the first argument is true, it is impossible to move the instruction pointer to run the code of the second argument. Hence, we cannot evaluate the second argument, even if we want to evaluate it.  But if you know how to evaluate both arguments when the first argument is true, I'd be happy to see the code. That does not mean that you add some code to the first argument so that it somehow grabs the second argument and does the evaluation, or some other similar odd  convolution.
...it is better to first strive for clarity and correctness and to make programs efficient only if really needed.

\"Getting Started with Erlang\"  version 5.6.2

Kazimir Majorinc

#14
It is because ELSE or THEN clause can have side effects. Look at this:


(if (< x y)
    (begin (inc lesser) x)
    (begin (inc greater) y))


If both clauses are evaluated, then both lesser and greater are incremented by one, while one probably wants only one of these. Lisp is not "pure functional" language. One can write code without side effects, but they're allowed, and if has to care about that.


(case exp-switch (const-1 body-1) [(const-2 body-2) ... ])

Yes, it has sense. It is simple idea that can make things cleaner.



No problems about question, ask as many times you need. These issues are not that obvious, really, there are some subtleties one cannot see on the first sight. But, it is easier to understand Newlisp than CL and Scheme.
http://kazimirmajorinc.com/\">WWW site; http://kazimirmajorinc.blogspot.com\">blog.