Redefine DEFINE?

For the Compleat Fan
Locked
Jeremy Dunn
Posts: 95
Joined: Wed Oct 13, 2004 8:02 pm
Location: Bellingham WA

Redefine DEFINE?

Post by 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

Code: Select all

(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
Posts: 253
Joined: Tue Aug 02, 2005 6:40 am
Contact:

Post by Fanda »

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.

Code: Select all

if not value? 'word [print "word is not set"]
(if (not (value? 'word)) (print "word is not set"))

Code: Select all

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/dictionary.html
http://www.rebol.com/docs/words/wmin.html

jrh
Posts: 36
Joined: Mon Nov 14, 2005 9:54 pm
Location: Portland, Oregon

Post by jrh »

Fanda wrote:In my opinion it sometimes makes things harder to read, because you have to remember exactly how many parameters function takes.
Well said.

Cyril
Posts: 183
Joined: Tue Oct 30, 2007 6:27 pm
Location: Moscow, Russia
Contact:

Post by Cyril »

Fanda wrote: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:

Code: Select all

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

Code: Select all

(make-pass abs sin)

(sin& abs& ceil x)
And, as a final touch, this:

Code: Select all

(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
Posts: 183
Joined: Tue Oct 30, 2007 6:27 pm
Location: Moscow, Russia
Contact:

Post by Cyril »

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:

Code: Select all

(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
Posts: 183
Joined: Tue Oct 30, 2007 6:27 pm
Location: Moscow, Russia
Contact:

Post by Cyril »

Third attempt (a synthesis of two previous):

Code: Select all

(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:

Code: Select all

(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
Posts: 95
Joined: Wed Oct 13, 2004 8:02 pm
Location: Bellingham WA

Post by Jeremy Dunn »

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
Posts: 36
Joined: Mon Nov 14, 2005 9:54 pm
Location: Portland, Oregon

Post by jrh »

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

Cyril
Posts: 183
Joined: Tue Oct 30, 2007 6:27 pm
Location: Moscow, Russia
Contact:

Post by Cyril »

Jeremy Dunn wrote: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).
Jeremy Dunn wrote: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
Posts: 95
Joined: Wed Oct 13, 2004 8:02 pm
Location: Bellingham WA

Post by Jeremy Dunn »

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

Code: Select all

(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
Posts: 183
Joined: Tue Oct 30, 2007 6:27 pm
Location: Moscow, Russia
Contact:

Post by Cyril »

Jeremy Dunn wrote:(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 : x). 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
Posts: 95
Joined: Wed Oct 13, 2004 8:02 pm
Location: Bellingham WA

Post by Jeremy Dunn »

This seems to do it

Code: Select all

(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
Posts: 429
Joined: Sun Dec 02, 2007 5:10 pm
Contact:

Post by itistoday »

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
Posts: 95
Joined: Wed Oct 13, 2004 8:02 pm
Location: Bellingham WA

Post by Jeremy Dunn »

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

Code: Select all

(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
Posts: 57
Joined: Wed Feb 13, 2008 6:41 pm

Re: Redefine DEFINE?

Post by Elica »

Jeremy Dunn wrote: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!!!
Image

(Sorry, I just could not resist)

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.

Well, I know this is a reply to the first post in the thread, but as I said above, I could not resist
Last edited by Elica on Mon Feb 25, 2008 10:00 am, edited 1 time in total.

Elica
Posts: 57
Joined: Wed Feb 13, 2008 6:41 pm

Post by Elica »

Jeremy Dunn wrote:(abs (ceil -3.4)) becoming (nest abs ceil -3.4)
By default I'm in brainstorming mode... and it works fine with few exceptions*... so excuse me if the idea is stupid, but Lisp alread has the exception, namely CAR+CDR=CADR, so ABS+CEIL=ABSCEIL etc. etc.

If you really want such feature, the translator may try to split unknown function names into known function names... automatically. But even in this case it might be good to have this functionality switchonable and switchoffable.



_________________________
* One notorious exception is when my wife asks me "Who will pay for this?" If I remain in brainstorming mode while answering, a desaster happens. No kidding.

Jeremy Dunn
Posts: 95
Joined: Wed Oct 13, 2004 8:02 pm
Location: Bellingham WA

Post by Jeremy Dunn »

Elica,

I don't know that I am breaking the LISP syntax at all unless you want to narrow it down. I have a function name "nest" that is followed by arguments and is enclosed in parentheses, I believe that is LISP syntax. The fact that I treat the argument : in a special manner may be unlispy to some but the overall form follows the pattern. Lutz also does this in the DEFINE function where he lets the comma be just another argument that is treated like I am using my colon. "Unlispy" forms are worth considering if they deal with often occuring structures in a more general way or provide some kind of conceptual clarity. I think LISP programmers are like abstract artists looking for that ultimate zen form of things. Other programmers just want to write code, lispers want to see the Om :)

Elica
Posts: 57
Joined: Wed Feb 13, 2008 6:41 pm

Post by Elica »

Oh, sorry for the misunderstanding.
I was talking about syntax breaking in the nest-less version.
I always like to see people experiment and find new applications of the tools they have...

Locked