Redefine DEFINE?

Started by Jeremy Dunn, January 30, 2008, 06:40:30 PM

Previous topic - Next topic

Jeremy Dunn

I wanted to revisit the subject of nested expressions, particularly functions that take a single argument only. For example, we could have



(sin (abs (ceil x)))



Fanda wrote a wonderful function he called PASS that enabled us to write this as



(pass (sin abs ceil) x)



That's pretty good but could we do better? I came to thinking about this when I was writing a redefinition of the NOT function as



(define-macro (n)
 (if (true? (args 0)) nil
     (nil? (args 0)) true
     (not (eval (if (= 1 (length (args)))) (args 0) (args))))
 ))


(n x) is equivalent to (not x). The difference occurs when you write an expression like (not (= x y)). Using the N function you can now rewrite this as (n = x y). Gets rid of parentheses. After doing this it occured to me that the same principle can be applied to ANY single argument function. This leads me to suggest the following: Let us have a slight redefinition of the DEFINE function so that if we are defining a function with a single argument that it behaves in this manner if it receives more than one argument. The first argument will be assumed to be the name of a function and all further arguments are considered to be its arguments. By doing this all single argument functions that exist and that will ever be written will have the property of nesting with each other. If this is so then we can simply write



(sin (abs (ceil x)))



as the more natural



(sin abs ceil x)



without the need for any PASS type of function at all. I think this solution is better than PASS because in this case when using PASS you eliminate 2 parentheses but must type PASS for a net gain of two characters. You would need a nesting at least 4 levels deep to finally get a net gain in typing. Doing it my way results in a gain for typing at any level of nesting since the behavior is part of the functions to begin with. If DEFINE cannot be changed then perhaps we could have a DEFINE-SINGLE command to deal with single argument functions.

Fanda

#1
Actually, I like parenthesis in newLISP and in any other LISP a lot. It makes it easy to see with what parameters have you called the function. Languages like REBOL have managed to get rid of some of the parenthesis by defining exactly how many parameters can you pass to a function.



In my opinion it sometimes makes things harder to read, because you have to remember exactly how many parameters function takes.
if not value? 'word [print "word is not set"]
(if (not (value? 'word)) (print "word is not set"))


string: "let's talk about REBOL"
if find string "talk" [print "found"]

(if (find string "talk") (print "found"))


By limiting the number of parameters you cannot define functions like 'min' with variable number of parameters. REBOL has 'min' for exactly 2 values only.



Every way has its advantages. Personally, I would keep parenthesis as much as possible :-)



Fanda



PS: Examples taken from:

http://www.rebol.com/docs/core23/rebolcore-4.html">http://www.rebol.com/docs/core23/rebolcore-4.html

http://www.rebol.com/docs/dictionary.html">http://www.rebol.com/docs/dictionary.html

http://www.rebol.com/docs/words/wmin.html">http://www.rebol.com/docs/words/wmin.html

jrh

#2
Quote from: "Fanda"In my opinion it sometimes makes things harder to read, because you have to remember exactly how many parameters function takes.


Well said.

Cyril

#3
Quote from: "Fanda"In my opinion it sometimes makes things harder to read, because you have to remember exactly how many parameters function takes.


But if we can clearly distinguish ordinary functions and pass-like functions, this problem is gone away. Consider this:


(define-macro (make-pass)
  (doargs (old)
    (eval
      (letex ((Old old)
              (New (sym (append (string old) "&"))))
        (define-macro (New)
          (Old (eval (args))))))))


And then this:


(make-pass abs sin)

(sin& abs& ceil x)


And, as a final touch, this:


(make-pass eval sym)

(define-macro (make-pass2)
  (doargs (old)
    (eval& letex ((Old old)
                  (New (sym& append (string old) "&")))
      (define-macro (New)
        (Old (eval (args)))))))


P.S. In fact, 'sin& abs& ceil' seems not very useful for me, but control structures like 'catch& begin', 'eval& letex' and '!& format' looks quite idiomatic.
With newLISP you can grow your lists from the right side!

Cyril

#4
Oops! I have completely missed all the letex logic! It's too, too easy to miscount how much times something is evaluated. All the examples was working only because the simple values (numbers, booleans) are evaluated to themselves. The correct definition is:


(define-macro (make-pass)
  (doargs (old)
    (letex ((Old old)
            (New (sym (append (string old) "&"))))
      (define-macro (New)
        (letex (Args (args))
          (Old Args))))))


P.S. Still confused. Thinking...
With newLISP you can grow your lists from the right side!

Cyril

#5
Third attempt (a synthesis of two previous):


(define-macro (make-pass)
  (doargs (old)
    (letex ((Old old)
            (New (sym (append (string old) "&"))))
      (define-macro (New)
        (Old (eval (args)))))))


I hope I have put it together right at last...



Update: In my humble opinion, chaining the arbitrary functions in this way doesn't make code cleaner. But some operations come in pairs very often, and for those cases is seems natural:


(make-pass catch not print)

(catch& while (read-line)
  (setq line (current-line))
  (if (not& empty? line)
    (print& format "*** %s ***n" line)
    (throw 'empty)))


I like this style! And you?
With newLISP you can grow your lists from the right side!

Jeremy Dunn

#6
I can appreciate Fanda's criticism  but I am only talking about the case where we are considering functions that take ONE argument only. Now Fanda says that something like (sin abs ceil x) becomes confusing because it all runs together. One way to handle this is simply to capitalize all functions that are not innermost as in (SIN ABS ceil x). This is simple and readable and closer to Cyril's approach which is pretty good too, but I admit that I would like to see the behavior built-in so that one doesn't have to declare anything first.

jrh

#7
Building it in makes the languages syntax more complex.  It is confusing and it also makes building expressions on the fly more difficult.

Cyril

#8
Quote from: "Jeremy Dunn"I would like to see the behavior built-in so that one doesn't have to declare anything first.


I disagree. I myself like this syntax and going to use it in my scripts, but it doesn't worth including in the language core. As another open source project author have said, ""It would be nice" is not enough reason". But, after some peer reviewing, it may be worth including in the some sort of standard library (as far as I know, Noodles pretends to play this role).


Quote from: "Jeremy Dunn"One way to handle this is simply to capitalize all functions that are not innermost as in (SIN ABS ceil x).


Syntax matters. When I experiment with this feature, I have at first using '!' instead of '&'. But I found I have regularly end up with (sin! abs! ceil! x). The bang symbol seems so... postfixish. Ampersand is better because you expect to see it between things. And capitalization is traditionally used for special variables of different kinds (META and so on).



And again: I do not like the idea of using this syntax for arithmetic functions. Of course nobody can prevent you from doing this, but I feel that it is most useful when two functions bound this way express some useful concept and not just happens to be unary. 'catch& begin', 'not& empty?', 'print& format' -- all these pairs are almost functions of their own. My suggestion is: use 'foo& bar' form when you can't decide whether this combination deserves it's own name and separate definition.
With newLISP you can grow your lists from the right side!

Jeremy Dunn

#9
Here is yet another way that one might do it where I used the function name N for NESTING rather than NOT.



(define-macro (n)
    (setq funcs (filter (fn (x)(ends-with x "&"))
                        (map rest
                            (map string
                               (map quote (filter symbol? (args))))))
          start (eval (slice (args)(length funcs)))
    )
    (dolist (op (reverse funcs))
      (setq start (eval (list (sym (trim op "" "&")) start)))
    )
    start
)


This way we are back to a functional method where instead of



(sin (abs (ceil x)))



it now becomes



(n sin& abs& ceil x)



This now becomes more like Cyril's method except that we don't have to make a pre-declaration. Perhaps this is also a reasonable compromise?

Cyril

#10
Quote from: "Jeremy Dunn"(n sin& abs& ceil x)


I you dislike both superfluous parentheses and declarations, try to define a macro which is called as (nest sin abs ceil ]. I am too lazy to write it myself just now, but it should be easy. Of course the name is up to you, I have choose nest just as an example.
With newLISP you can grow your lists from the right side!

Jeremy Dunn

#11
This seems to do it



(define-macro (nest)
    (setq ind   (find : (map eval (args)))
          funcs (slice (args) 0 ind)
          vars  (slice (args)(+ ind 1))
          start (eval (append (list (last funcs)) vars))
          funcs (rest (reverse funcs))
    )
    (dolist (op funcs)
      (setq start (eval (list op  start)))
    )
    start
)

itistoday

#12
Nifty!  I think I like this last approach best!  Now it's completely clear what you're doing, you're telling whoever is reading the code that "this is non-standard, please check the nest function", and you don't have to introduce any weird &'s.  Great job!
Get your Objective newLISP groove on.

Jeremy Dunn

#13
Further fooling around made me realize I forgot to localize my variables, this I hope will be the final word:



(define-macro (nest)
  (local
     (ind funcs vars start)
     (if (setq ind (find : (map eval (args))))
       (begin
         (setq funcs (slice (args) 0 ind)
               vars  (slice (args)(+ ind 1))
               start (eval (append (list (last funcs)) vars))
               funcs (rest (reverse funcs))
         )
         (dolist (op funcs)
            (setq start (eval (list op start))))
         start
       )
       (begin
         (setq start (eval (append (list (args 1))
                                         (slice (args) 2))))
         (eval (list (args 0) start))
       ))))


This version has the added detail of enabling you to leave out the colon if you have only a single nesting such as (abs (ceil -3.4)) becoming

(nest abs ceil -3.4) rather than (nest abs ceil : -3.4). It can be written either way but I wanted to get rid of that colon for the most common nesting of just one level deep. If the colon is missing the first two arguments are considered to be functions with the rest being arguments to the 2nd function, any other case requires the colon to separate the arguments.

Elica

#14
Quote from: "Jeremy Dunn"If this is so then we can simply write



(sin (abs (ceil x)))



as the more natural



(sin abs ceil x)


Great! A few more steps and you will reinvent Logo!!!

http://forum.skycode.com/img/63.gif">



[size=59](Sorry, I just could not resist)[/size]



Now, seriously, one of the beauty of Lisp (in the eye of a non-Lisper like me) is that its syntax is uniform! By introducing such feature you are breaking the Lisp syntax by adding an exception. However, I cannot judge whether this is good or bad... because it is both of them.



It would be consistent if you have the same feature for functions with more parameters, but going this way you will end up with Logo.



[size=59]Well, I know this is a reply to the first post in the thread, but as I said above, I could not resist[/size]