Nesting and Booleans

Started by JeremyDunn, July 04, 2010, 05:02:31 PM

Previous topic - Next topic

JeremyDunn

My mind wanders around a lot and I find that I have something more to say about function nesting and how it turned out to be involved with boolean statements. To see the relation takes a while but please bear with me.



If we have a nested statement such as (sin (cos 0.5)) we instinctively want to simplify it to (sin cos 0.5) but are frustrated in that the current syntax doesn't allow us to do that. So the good lisper can write himself a function that we will call NEST and then be able to write something like (nest sin cos 0.5). This may be elegant but in most cases does not satisfy because we must type another five characters (don't forget the space). Often the nesting you actually run across only goes for two levels and it is just easier to type the two parentheses, this approach only starts to have value at three levels of nesting. We can't get rid of that desire to type (sin cos 0.5)! A lisper always wants to do the least typing possible. For fun I wrote this function

(define-macro (nest)
  (setq op (args 0) rargs (rest (args)))
  (op (eval (if (symbol? (rargs 0))
                 (begin
                   (setq r   (rest (rargs))
                         lst (rargs -1)
                         r   (if (symbol? lst)
                                 (setf (r -1)(list lst))
                                 r
                             )
                   )
                   (apply (rargs 0)(map eval r))
                 )
                 (rargs 0)
             ))))

I hope someone writes a better version of this because I am pretty sure I didn't take the best approach. This function takes advantage of the fact that all functions that are capable of nesting only have a single argument. And we can use this frame to redefine all of the existing single argument functions if we wish. Here is a demonstration example with the SIN and COS functions:

(define-macro (sn)   (nest sin  ))
(define-macro (cs)   (nest cos  ))

Using these new functions you can now write (sn cs 0.5) and get the desired result. If you redefine all of NewLisp's single argument functions and your own this way you can have built-in nesting the way God intended!



However I ran across a case where making all single argument functions act this way is undesirable. I found that boolean functions that take single arguments should be treated in a different manner. I came up with this fun little piece of code:

(define (bool-if func x A B)
  (if (or A B)
      (eval (if (func x) A B))          
      (func x)))

What this function does is allow any boolean function to double as its own if/then/else conditional statement. How does this work? Suppose we define a new version of the INTEGER? function as

(define (new-integer? x A B)
   (bool-if integer? x A B))

Now if we write (new-integer? 3) this behaves the same way as integer? does. But if we were to write (new-integer? 3 "A" "B") it now behaves like (if (integer? 3) "A" "B"). We now don't have to write the IF statement!



So we have two conflicting desires here, we can't make all single argument functions have nesting behavior and be able to have built-in conditionals at the same time. I have concluded that this is not a problem in practice because almost all nested statements that I have run across do not tend to have conditionals in them or at least rarely. Therefore I propose that all boolean functions (functions that return true or nil) have built-in conditional capability and that all other single argument functions have built-in nesting. This could be further advanced by having two new functions DEFINE-BOOL and DEFINE-NEST so that users could define such functions right from the beginning. How would this work? Let us say that we want to define a new boolean function that determines whether a number is even or odd. We will call this function EVEN?. I envisage writing something like

(define-bool (even?)
  (zero? (% $X 2))
)

The user only has to supply the name of the function and the code for the logical test. $X represents an internal variable that  stands for the single argument that the function takes for the default case. All of the rest of the overhead is taken care of by DEFINE-BOOL. A similar technique would be used for a DEFINE-NEST function.



I believe that there cumulative advantages to implementing both of these approaches to NewLisp. It doesn't break previous code but adds a great enhancement. Wouldn't it be nice to get rid of a lot of IF statements? Anyone have any thoughts on the matter?

itistoday

#1
I think it's cool, but I'm not sure this would result in very clear code (for others reading it). People coming to newLISP from other Lisps would be baffled by this. Does the advantage of not having to write an 'if' make it worth it? I'm not sure...



BTW, Clojure has a builtin function called 'comp' (for composition) that is essentially your 'nest' macro. I think explicitly calling nest/comp is the better way of doing it, as that's more "idiomatic".



Some bugs I noticed: new-integer? and bool-if should be macros so that they don't evaluate their arguments (and you might want to use define-smacro, see below), there are missing calls to parameters in the sn/cs functions, and your nest macro is polluting its calling context by not using 'let' to set 'op', rargs, r, and lst.


(define-macro (define-smacro)
    (let (_temp1 (append (fn-macro) (cons (1 (args 0)) (rest $args))) _temp2 (args 0 0))
        (def-new '_temp1 (sym _temp2 _temp2))
)
)
Get your Objective newLISP groove on.

itistoday

#2
I can't find the original thread where define-smacro was discussed....



Only thing I've been able to find that discusses the hygenic macro issue is here:



http://newlispfanclub.ryon.webfactional.com/forum/viewtopic.php?f=5&t=1080">http://newlispfanclub.ryon.webfactional ... f=5&t=1080">http://newlispfanclub.ryon.webfactional.com/forum/viewtopic.php?f=5&t=1080
Get your Objective newLISP groove on.

itistoday

#3
I should elaborate that beyond being non-idiomatic, it would make it difficult to tell whether the code you're seeing is function composition, or a call to a function that's accepting several arguments, which is why it's probably better to be explicit.
Get your Objective newLISP groove on.

Kazimir Majorinc

#4
Cyril Slobin http://slobin.livejournal.com/148287.html">did something similar. I'll cite whole his post, translated by Google:



True fan Lisp brackets not frighten . Still, sometimes , in some contexts , they seem superfluous. Let's make a useful idiom . Writing a macro of the six lines]



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

Enumerate the needed functions]


(make-pass catch not print)
And we use ]



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

In my opinion , bad happened. I like it. Ampersand - he all of a certain infix , easy to believe that it combines two functions into one. In Common Lisp this transfer will almost certainly be on the scheme with its hygienic macros - not sure.



I also Code Select
; The composition of the functions is one of the basic mathematical
; operations. In this post, I'll try to define composition of
; functions and macros (Newlisp macros=fexprs) in Newlisp.
;
; Such composition should satisfy the following:
;
;     ((composition 'f1 ... 'fn) _ _ _) = (f1 (f2 ... (fn _ _ _)))
;
; for all functions and macros.
;
; It wasn't that easy as I thought. First, I limited myself on the
; case of the composition of two functions. After some experimentation
; I came to that:

(set 'compose2 (lambda(f g)
                 (expand (lambda-macro()(letex((L (cons 'g (args))))(f L)))
                          'f 'g)))

; I tested it on simple example:

(println (compose2 'sin 'cos))

; (lambda-macro ()
;  (letex ((L (cons 'cos (args)))) (sin L)))

(println ((compose2 'sin 'cos) 3) (sin (cos 3))) ; OK, it works.

; Then I tested it on two special, well, entities, i.e. identity
; function and identity macro:

(set 'I (lambda(x)x))
(set 'IM (lambda-macro(x)x))

(println ((compose2 'I 'sin) 3)) ; 0.1411200081, as it should be, i.e. (I (sin 3))
(println ((compose2 'sin 'I) 3)) ; 0.1411200081, as it should be, i.e. (sin (I 3))
(println ((compose2 'IM 'sin) 3)) ; (sin 3), as it should be, i.e. (IM (sin 3))
(println ((compose2 'sin 'IM) 3)) ; 0.1411200081, as it should be (sin (IM 3))

; OK; it appears it worked. Now I'll have to solve multi-version,
; I.e. composition of many functions or macros

(set 'compose (lambda()
                 (case (length (args))
                    (0 I)               ; because (I (S x)) <=> (S x),
                                        ; no matter if S is function or macro
                                        ; so I can be always added from the left.
                    (1 (first (args)))
                    (2 (apply compose2 (args)))
                    (true (compose2 (first (args)) (apply compose (rest (args))))))))

(println ((compose sqrt) 65536)) ; 256
(println ((compose sqrt sqrt) 65536)) ; 16
(println ((compose sqrt sqrt sqrt) 65536)) ; 4


; OK, it works as well. However, result of the composing is
; rather complicated because of recursive definition

(println (compose 'f1 'f2 'f3 'f4))

; (lambda-macro ()
;   (letex ((L (cons '(lambda-macro ()(letex ((L (cons '(lambda-macro ()(letex ((L (cons 'f4 (args))))
;                                                                              (f3 L)))
;                                                      (args))))
;                                             (f2 L)))
;                    (args))))
;         (f1 L)))
;
;
; If iteration is used for definition of compose, then composition
; can be both shorter and faster - but not more elegant.
http://kazimirmajorinc.com/\">WWW site; http://kazimirmajorinc.blogspot.com\">blog.

JeremyDunn

#5
For those that want to play with the boolean statements it doesn't involve changing too many functions. Here is my list:

;Redefine all of NewLisp's boolean functions
(define (array??     x A B)(bool-if array?     x A B))
(define (atom??      x A B)(bool-if atom?      x A B))
(define (context??   x A B)(bool-if context?   x A B))
(define (directory?? x A B)(bool-if directory? x A B))
(define (empty??     x A B)(bool-if empty?     x A B))
(define (file??      x A B)(bool-if file?      x A B))
(define (float??     x A B)(bool-if float?     x A B))
(define (global??    x A B)(bool-if global?    x A B))
(define (inf??       x A B)(bool-if inf?       x A B))
(define (integer??   x A B)(bool-if integer?   x A B))
(define (lambda??    x A B)(bool-if lambda?    x A B))
(define (legal??     x A B)(bool-if legal?     x A B))
(define (list??      x A B)(bool-if list?      x A B))
(define (macro??     x A B)(bool-if macro?     x A B))
(define (NaN??       x A B)(bool-if NaN?       x A B))
(define (nil??       x A B)(bool-if nil?       x A B))
(define (null??      x A B)(bool-if null?      x A B))
(define (number??    x A B)(bool-if number?    x A B))
(define (primitive?? x A B)(bool-if primitive? x A B))
(define (protected?? x A B)(bool-if protected? x A B))
(define (quote??     x A B)(bool-if quote?     x A B))
(define (string??    x A B)(bool-if string?    x A B))
(define (symbol??    x A B)(bool-if symbol?    x A B))
(define (true??      x A B)(bool-if true?      x A B))
(define (zero??      x A B)(bool-if zero?      x A B))
(define (not??       x A B)(bool-if not        x A B))

The booleans work out nicely from a naming standpoint in that all I had to do was add a second question mark on the end to make them distinct. A concern was expressed that treating booleans this way might make code unreadable or confusing to people from other LISPs. That may be so but one might say the same thing about implicit indexing. The point here is that any language has syntax quirks that have to be learned. There is no point in creating a language if it does not attempt to do one or more things differently to keep advancing better notions. In practice I have tried using these on a code module of mine and didn't find it the least bit confusing but I am admittedly a biased observer. It has the virtue of not forcing you to change your ways but allows the opportunity for those that are bent in that direction just as one is not forced to use implicit indexing but eventually discovers its awfully nice to have sometimes.



The make-pass method of nesting I find the most acceptable because it allows brevity for short nestings and the & on the end of the functions make it clear that something is going on. I do not see composing being used in the sense that Kaz does it because it does not have the simplicity of appearance that make-pass does. Kaz's method may be more traditional but it is too much trouble for short nestings, which is the typical case. I think people will just type (sin (cos x)) rather than go through all the extra trouble just to be traditional. The nestings have to be too deep to gain an advantage in typing. Now suppose we end up taking the make-pass approach and write (sin (cos x)) as (sin& cos& x), have we gained anything? The total characters typed was the same so we gained nothing in typing. Arguably we might say that it looks cleaner in that the parentheses seem more distracting than adding the two &. It takes a second level of nesting before we gain a definitive advantage. But if we rewrite the functions to nest as part of their nature then we can write (sin cos x) and gain advantage at the first nesting. In Paul Graham's Arc language one could write the statement as (sin:cos x) which allows us to type one less symbol than make-pass. But I think its even better to not have to type extra symbols at all. A change in syntax could be nice. What if we could write something like (sin cos | x) where the vertical line delineated nesting and a separation from the functions and the arguments. This would allow nesting to be visually distinct, it would require only one symbol for any level of nesting and give advantage at the first nesting. Of course only Lutz could make this happen unless he gives us reader macros to play with.

itistoday

#6
I haven't played with it much and I'm not sure it's a very efficient solution, but you might want to take a look at the 'reader-event' function, which may allow you to implement the | syntax.
Get your Objective newLISP groove on.

Kazimir Majorinc

#7
The advantage of the Lisp concept is its regular syntax, which makes metaprogramming easier. If Lisp didn't "catch on", it is because either (a) there is little advantage of meta-programming, so Lisp was not good idea, or (b) metaprogramming is sound concept, but it is hard to use, and Lisp still didn't accumulated enough advantage in techniques, flexibility, libraries etc. But, eventually, it can develop to that point.



Anycase, the price or the regularity, lot of parentheses, has to be paid. Lisp users are tempted to humanize the syntax and all Lisps appear to do that to some degree (I'm not sure about Pico). By doing that, one makes programming more comfortable, but in the same time metaprogramming become even harder, and supposed advantage of Lisp more questionable. So, perhaps it might be the best to fight desire to write (sin cos 0.5). If not, then maybe something like this can help]



(define-macro (make-pass-adapted)
  (doargs (arg)
    (letex ((Old arg)
            (Old.original (sym (append (string arg) ".original")))
            (New (sym (string arg))))
      (setf Old.original Old)
      (constant 'New (lambda-macro()
                        (if (> (length (args)) 1)
                            (Old.original (eval (args)))
                            (Old.original (eval (first (args))))))))))
       
(make-pass-adapted sin cos)

(println (sin.original (cos.original 3)) " = " (sin cos 3))
(println (cos.original (sin.original (cos.original 3))) "=" (cos sin cos 3))


Did you noticed this]



"In my opinion , bad happened. I like it. "
http://kazimirmajorinc.com/\">WWW site; http://kazimirmajorinc.blogspot.com\">blog.