Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - William James

#1
Whither newLISP? / lexical-let
October 06, 2013, 09:44:14 PM
Before version 24.1, EMACS Lisp was dynamically scoped; now lexical scoping

is an option.  When a closure was needed, it was customary to load the

CL package and use lexical-let.



It would be very nice if lexical-let were added to newLISP. Closures

could be created more easily, by a method that is more readily

understood by users of other dialects of Lisp. It would make it easier

to demonstrate the power of newLISP to potential converts.



There is a book titled "Let over Lambda" that demonstrates the

advanced use of closures; when lexical-let is added to newLISP,

converting the code in the book will be a trivial task.



The amount of code added to the newLISP distribution would be

miniscule. It's much better to have the facility bundled with the

language rather than having to include the code for the macro when

you post an example in a forum like Stackoverflow. Brevity will make a

better impression on interested programmers.



I know that closures can be created using contexts, but it seems to me

that most programmers will find it easier to use a lexical let; they

are used to doing it that way in other Lisps and in Scheme. Since it

makes programming easier and adding it to the language would be dead

simple, it would be a shame not to include it.



To get the ball rolling, here is my attempt at an implementation.

(uuid is used instead of gensym.)


(context 'lexical-let)
(define-macro (lexical-let:lexical-let varval-pairs)
  (let (body (cons 'begin (args))
        alist (map (fn(x) (list (x 0) (sym (string 's (uuid))) (eval (x 1))))
                   (explode varval-pairs 2)))
    (bind (map rest alist))
    (dolist (x alist) (set-ref-all (x 0) body (x 1)))
    (eval body)))
(context MAIN)

;; Example

(define (make-fibber)
  (lexical-let (a 0  b 1)
    (fn() (swap a b) (++ b a) a)))

newLISP's popularity cannot be decreased, and may very well be

increased, by adding an implementation of this.
#2
(macro (assoc-push Alist Key Val)
  (let (key Key  val Val)
    (or
      (catch
        (push val (or (lookup key Alist) (throw nil)) -1))
      (push (list key (list val)) Alist -1))))

(macro (assoc++ Alist Key Val)
  (let (key Key  val Val)
    (or
      (catch
        (++ (or (lookup key Alist) (throw nil)) (or val 1)))
      (push (list key (or val 1)) Alist -1))))


Example:


(define (word-counts wlist)
  (let (counts '())
    (dolist (word wlist)  (assoc++ counts word))
    counts))

(word-counts (find-all "[a-z]+" (lower-case
  {"The time has come," the Walrus said,
  "To talk of many things:
  Of shoes--and ships--and sealing-wax--
  Of cabbages--and kings--
  And why the sea is boiling hot--
  And whether pigs have wings."})))

(("the" 3) ("time" 1) ("has" 1) ("come" 1) ("walrus" 1) ("said" 1)
 ("to" 1) ("talk" 1) ("of" 3) ("many" 1) ("things" 1) ("shoes" 1)
 ("and" 5) ("ships" 1) ("sealing" 1) ("wax" 1) ("cabbages" 1)
 ("kings" 1) ("why" 1) ("sea" 1) ("is" 1) ("boiling" 1) ("hot" 1)
 ("whether" 1) ("pigs" 1) ("have" 1) ("wings" 1))
#3
newLISP in the real world / Re: Possible bug in sort
December 28, 2012, 12:53:40 PM
I just tested the speed using Lutz's 'fun workaround.



The composed function is just as fast as the lambda (fn (a b) (< (last a) (last b))).



Perhaps I should explain the name f<g=.



If I remember correctly, J (descended from APL) gives names to its operators that compose functions like fork, atop, cap, hook.  It's not easy to remember exactly what they do.  So I'm trying to use a naming convention that shows how the operator works.  The function composition used above could be diagrammed this way:

    f
    /
   g   g
   |   |
   a   b

The name looks somewhat like the diagram rotated 90 degrees counterclockwise.



Another example:

    f
    /
   g   h
     /
     a

This could be used to calculate the average of a list of numbers.
((f<gh> / sum length) numbers)
It seems rather nice to program without explicit, named parameters.
#4
newLISP in the real world / Re: Possible bug in sort
December 28, 2012, 12:07:59 PM
Thanks for the fast and helpful reply.



Function composition is a powerful and fun technique.

I'm hoping that
(sort (copy test-list) (f<g= < last))
won't be slower than
(sort (copy test-list) (fn (a b) (< (last a) (last b))))
#5
newLISP in the real world / Possible bug in sort
December 28, 2012, 09:08:22 AM

;; Function composition.
(define (f<g= f g)
  (expand (fn (a b) (f (g a) (g b))) 'f 'g))

((f<g= < last) '(a 2) '(b 3))
 ==> true

((f<g= < last) '(b 3) '(a 2))
 ==> nil

(set 'lst '((b 5) (a 2)))
(sort lst (f<g= < last))
 ==> ((a 2) (b 5))

(set 'lst '((a 2) (b 5)))
(sort lst (f<g= < last))
 ==> ((a 2) (b 5))


(set 'lst '((c 4) (b 5) (a 2)))
(sort lst (f<g= < last))

  [ crashes ]

#6
Here's a REPL that I cobbled together.  It probably has bugs, but it works pretty well for me.



(println "Quit with (exit).")

(define (input__complete? str)
  (if (or (find "^s*$" str 0)
          (find "^s*;[^n]*$" str 0))
    true
    (let (error-message ""
          scanned 0)
      (if
        (catch
          (begin
            (read-expr str)
            (setq scanned $0))
          'error-message)
        (input__complete? (slice str scanned))

        (when (find {^ERR: symbol expected}
               error-message 0)
          (replace "(?s:^ERR: |[rn].*$)" error-message "" 0)
          (throw-error error-message)
        )

        false))))


(let ((repl__line "")
      (repl__accum '())
      (repl__expression ""))
  (do-while true
    (unless
      (catch
        (begin
          (when (empty? repl__accum)
            (unless (= 'MAIN (context)) (print (context)))
            (print ": "))
          (setq repl__line (read-line))
          (push repl__line repl__accum)
          (setq repl__expression
            (join (reverse (copy repl__accum)) "n"))
          (if (input__complete? repl__expression)
            (begin
              (setq repl__accum '())
              (setq repl__expression
                (read-expr (string "(begin " repl__expression ")")))
              (println (eval repl__expression)))))
        'error-message)
      (setq repl__accum '())
      (println error-message))))

#7

> (set 'a (fn (n) (if (zero? n) 1 (* n (a (- n 1))))))
(lambda (n)
 (if (zero? n)
  1
  (* n (a (- n 1)))))
> (a 5)
120
#8
newLISP in the real world / Re: list to values
December 21, 2012, 07:11:55 AM

(define (my-func s1 s2 a b c)
  (dup (string s1 s2 a b) c))

> (my-func "o" "-" 1984 9 3)
"o-19849o-19849o-19849"

> (apply my-func (cons "o" (cons "-" '(1984 9 3))))
"o-19849o-19849o-19849"

> (apply my-func (append '("o" "-") '(1984 9 3)))
"o-19849o-19849o-19849"
#9

(define (half-string str)
  ((div (length str) 2) str))

(while (read-line)
  (println (half-string (current-line))))
#10
It seems that gensym isn't really needed anymore.



One can simply do (sym (uuid)).
#11
QuoteI'm processing some text so that various characters are replaced with alternative formulations. The problem is that the curly braces in the original text need to be replaced with letteropenbrace{} but the backslashes need to be replaced with letterbackslash{}, and I can't do both these operations independently.



One possible solution that occurred to me was to replace the '{' with some unique text that couldn't possibly occur in newLISP source code, then to replace it again once I'd finished the other characters. But is this the most efficient way of doing a sequence of changes? It looks inefficient as well, but perhaps it isn't.


I think that the best solution is to pass through the string only once.  That way, the replacements won't be replaced.



(define (translate ch)
  (case ch
    ({} {letterbackslash{}})
    ({$} {letterdollar{}} )
    ({#} {letterhash{}})
    ({!} {letterexclamationmark{}} )
    ({|} {letterbar{}})
    ({@} {letterat{}})
    ({^} {letterhat{}})
    ("%" {letterpercent{}} )
    ("/" {letterslash{}} )
    ("<" {letterless{}} )
    (">" {lettermore{}} )
    ("~" {lettertilde{}} )
    ("&" {letterampersand{}})
    ("?" {letterquestionmark{}})
    ("_" {letterunderscore{}})
    ("'" {lettersinglequote{}})
    ("{" {letteropenbrace{}})
    ("}" {letterclosebrace{}})
    (true ch)))

(setq text {A set {2 3 5 e f} of numbers & letters.})

(replace "." text (translate $0) 0)
#12

;; using sym for simulating hash tables

(set (sym "John Doe" 'MyDB') 1.234)


There should not be a single-quote after 'MyDB.
#13
Interesting approach; I'm not very familiar with letex.



Since we are putting each macro in its own context, I think you can safely use func instead of _func.



I like "(if $args" instead of "(if (empty? (args))"; shorter and cleaner.



You gave me the idea of just using one eval:



(context '->>)
(define-macro (->>:->> E form)
  (eval
    (if $args
      (cons '->> (cons (list '->> E form) (args)))
      (if (list? form)
        (push E form -1)
        (list form E)))))

(context '->)
(define-macro (->:-> E form)
  (eval
    (if $args
      (cons '-> (cons (list '-> E form) (args)))
      (if (list? form)
        (cons (first form) (cons E (rest form)))
        (list form E)))))
(context MAIN)


I find that using these macros is fun.  Clojure comes with them, and something similar can be easily defined in OCaml.
#14
newLISP in the real world / Macros for pipelining
April 07, 2012, 09:35:37 AM
Here are two "threading" or pipelining macros, similar to those in Clojure:

(context '->>)
(define-macro (->>:->> E form)
  (if (empty? (args))
    (if (list? form)
      (eval (push E form -1))
      (eval (list form E)))
    (eval (cons '->> (cons (list '->> E form) (args))))))

(context '->)
(define-macro (->:-> E form)
  (if (empty? (args))
    (if (list? form)
      (eval (cons (first form) (cons E (rest form))))
      (eval (list form E)))
    (eval (cons '-> (cons (list '-> E form) (args))))))

(context MAIN)


Consider this sequential application of three functions:


: (exp (sqrt (abs -3)))
5.652233674


Using one of the macros, the functions appear in the same order that they are applied and fewer parentheses are needed:

: (-> -3 abs sqrt exp)
5.652233674

The -> macro feeds the item as the first argument to the function:

: (-> 8 (div 4))
2

The ->> macro feeds the item as the last argument to the function:

: (->> 8 (div 4))
0.5

Let's extract the values from an association list, select only those that are less than 50, and add them up.


(setq alist '((a 29)(b 25)(c 21)(d 64)))
: (->> alist (map last) (filter (curry > 50)) (apply +))
75
#15
newLISP in the real world / Re: Yet another REPL
April 07, 2012, 06:52:39 AM
Slightly improved.  To quit, type "(exit)".



(define (input__complete? str)
  (if (or (find "^s*$" str 0)
          (find "^s*;[^n]*$" str 0))
    true
    (let (error-message ""
          scanned 0)
      (if
        (catch
          (begin
            (read-expr str)
            (setq scanned $0))
          'error-message)
        (input__complete? (slice str scanned))
        (when (find {^ERR: symbol expected}
               error-message 0)
          (replace "(?s:^ERR: |[rn].*$)" error-message "" 0)
          (throw-error error-message)
        )
        false))))

(let ((repl__line "")
      (repl__accum '())
      (repl__expression ""))
  (do-while true
    (unless
      (catch
        (begin
          (when (empty? repl__accum)
            (unless (= 'MAIN (context)) (print (context)))
            (print ": "))
          (setq repl__line (read-line))
          (push repl__line repl__accum)
          (setq repl__expression
            (join (reverse (copy repl__accum)) "n"))
          (if (input__complete? repl__expression)
            (begin
              (setq repl__accum '())
              (setq repl__expression
                (read-expr (string "(begin " repl__expression ")")))
              (println (eval repl__expression)))))
        'error-message)
      (setq repl__accum '())
      (println error-message))))