• 0 Posts
  • 8 Comments
Joined 1 year ago
cake
Cake day: October 17th, 2023

help-circle

  • Because eval is what defines what the syntax of the language is, while apply has nothing to do with syntax at all but only semantics. Imagine a lisp which was in latin. You would need to change eval but not apply. Or if there are larger changes to syntax: apply remains unchanged in all these cases.

    In same way, with a slight variant of apply (it must get an environment object as well as function and arguments) then you can change the scoping rules of your Lisp while making no changes at all to eval but only to apply. Such an evaluator is here (in Racket):

    #lang racket
    
    ;;;; Tiny lisp evaluator which can have dynamic or lexical scope
    ;;;
    ;;; Based on tlisp
    ;;;
    
    (module+ test
      (require rackunit))
    
    (define empty-environment '())
    
    (define (extend-environment env variables values)
      (match* (variables values)
        [('() '())
         env]
        [((cons (? symbol? variable) more-variables)
          (cons value more-values))
         (extend-environment (cons (cons variable value) env)
                             more-variables more-values)]
        [('() _)
         (error 'extend-environment "not envough variables")]
        [(_ '())
         (error 'extend-environment "not enough values")]
        [(_ _)
         (error 'extend-environment "what even is this")]))
    
    (define lexical-scope? (make-parameter #t))
    
    (define (bound? var env)
      (and (assq var env) #t))
    
    (define (value-of var env)
      (unless (bound? var env)
        (error 'value-of "~S is unbound" var))
      (cdr (assq var env)))
    
    (struct primitive
      (fn))
    
    (struct function
      (formals body environment))
    
    (define (evaluate form environment)
      (match form
        [(? symbol? s)
         (value-of s environment)]
        [(list op arguments ...)
         (case op
           [(quote)
            (match arguments
              [(list thing)
               thing]
              [_
               (error 'evaluate "ill-formed quote")])]
           [(lambda λ)
            (match arguments
              [(list (list (? symbol? formals) ...) body ...)
               (function formals body environment)]
              [_
               (error 'evaluate "ill-formed λ")])]
           [(if)
            (match arguments
              [(list test then else)
               (if (evaluate test environment)
                   (evaluate then environment)
                   (evaluate else environment))]
              [(list test then)
               (when (evaluate test environment)
                 (evaluate then environment))]
              [_
               (error 'evaluate "ill-formed if")])]
           [else
            (applicate (evaluate op environment)
                       (map (λ (a) (evaluate a environment))
                            arguments)
                       environment)])]
        [(? cons?)
         (error 'evaluate "evaluating a cons which is not a list")]
        ['()
         (error 'evaluate "null")]
        [something
         something]))
    
    (define (applicate f arguments dynamic-environment)
      (match f
        [(primitive fn)
         (apply fn arguments)]
        [(function formals body lexical-environment)
         (define extended-environment (extend-environment
                                       (if (lexical-scope?)
                                           lexical-environment
                                           dynamic-environment)
                                       formals arguments))
         (let evb ([value (void)]
                   [forms body])
           (match forms
             ['()
              value]
             [(cons form more)
              (evb (evaluate form extended-environment)
                   more)]))]
        [_
         (error 'applicate "what even is this")]))
    
    (module+ test
      (check-true
       (evaluate
        '((λ (cons car cdr nil)
            ((λ (c)
               (if (eqv? (car c) 1)
                   (if (eqv? (cdr c) 2)
                       #t
                       #f)
                   #f))
             (cons 1 2)))
          (λ (a b)
            (λ (s) (s a b)))
          (λ (c)
            (c (λ (a b) a)))
          (λ (c)
            (c (λ (a b) b)))
          (λ ()))
        (extend-environment empty-environment
                            '(eqv?) (list (primitive eqv?))))))
    
    (module+ test
      (check-eqv?
       (parameterize ([lexical-scope? #t])
         (evaluate
          `((λ (x)
              (((λ (x)
                 (λ () x))
                20)))
            10)
          empty-environment))
       20)
      (check-eqv?
       (parameterize ([lexical-scope? #f])
         (evaluate
          `((λ (x)
              (((λ (x)
                 (λ () x))
                20)))
            10)
          empty-environment))
       10))
    



  • Please stop with this rubbish narrative - you even say later that Medley supports Common Lisp, and the Maclisp Lisp machine lineage did too. They’re all von Neumann, so not that profoundly different either.

    I was not born in the days when d-machines were really alive, and also on the wrong side of the iron curtain. but I have a friend who used them quite seriously and also Genera. He does not read reddit, but I asked him. He wrote this:

    Oh that’s funny: that person has never used a d-machine and has no idea what they’re talking about at all. D-machines were about as utterly different from Unixoid systems as it’s possible to be and still be a computer. I mean, really, they were just this completely alien thing that clearly had some influence from the fae in their design and working.

    And the whole ‘they ran Common Lisp’: they ran Common Lisp eventually. I think Lyric had some CL but not very well, Medley had a pretty complete CL without CLOS which probably arrived later. They were essentially dead by the time they had CL.

    Describing the current Interlisp thing as a ‘graft’ is pretty good. Maiko implements something that looks enough like a d-machine that sits on top of a Unix box. In the early days of it (we never used it very seriously once the hardware died as it was very expensive then and seemed like a mad waste of a perfectly good Sun) you could take a sysout from a d-machine and boot it on the emulator and it would be fine.

    But actually it’s not really a graft: it’s different from that. A graft is something you do to attach an apple tree to the root system of another apple tree. But they’re both apple trees. What the Interlisp project is doing is attaching a butterfly to the root system of an apple tree or something. In such a way that the butterfly doesn’t notice it’s now part of a plant.

    Genera was much less alien, but still pretty alien.


  • In no case does remove modify the original object or any of its elements.

    remove will traverse the object looking for elements which match thing to be removed

    • if are no such elements it may return either a copy of the original object with same type or the original object;
    • if are such elements it will return a new object which has those elements removed but which is same type as original object…

    In second case the returned object may share structure with original object. This really applies only for lists.

    Example for lists, it is allowed that

    (let ((x (list 1 2 3)))
      (eq (remove 1 x) (cdr x)))
    

    may return true. Is actually unlikely it will do so, because implementation of remove which can do this check would be quite hard to see how it would be better, but is allowed to be the case.

    Here is implementation of simple version of remove which works only on lists and which does not ever share:

    (defun remove-from-list (e l)
      (loop for thing in l
            when (eql thing e) collect thing))
    

    Here is version which does share, you can see why this is nasty.

    (defun remove-from-list (e l)
      (labels
          ((rfll (lt et)
             (cond
              ((null lt)                    ;done with list
               '())
              ((null et)                    ;done with es
               lt)
              ((eq lt et)                   ;hit first e
               (rfll (rest lt) (member e (rest lt))))
              (t                            ;are es but not yet
               (cons (first lt) (rfll (rest lt) et))))))
        (rfll l (member e l))))
    

    Perhaps there are better approaches than this one: certainly there are which can be made tail recursive or iterative, but need to keep looking to know when to copy is expense.


  • What is purpose of this? Optimize a function which uses a terrible algorithm? Why not use a good algorithm? In Racket for instance

    (define (memoize/1 f)
      (define h (make-hasheqv))
      (λ (a)
        (hash-ref! h a (thunk (f a)))))
    
    (define fib
      (memoize/1
       (λ (x)
         (if (< x 2) 
             1
             (+ (fib (- x 2)) 
                (fib (- x 1)))))))
    

    And now

    > (time (fib 39))
    cpu time: 0 real time: 0 gc time: 0
    102334155
    > (time (fib 50000))
    cpu time: 77 real time: 88 gc time: 29
    174387413787277868303885543...
    

    (10,000 digit number, results are from cold Racket so memo table was empty initially.)

    Is entertaining then to try to compute say (log (fib 500000) 10). Bignums tend to get pretty slow then.


  • nil being self-evaluating is not strange. Since nil is a symbol it can be defined as a constant. So there is nothing to stop a Lisp implementation saying (in CL):

    (defconstant nil 'nil)
    

    just the same as

    (defconstant t 't)
    

    What is strange is these things:

    strange thing true in CL? true in elisp? true in Scheme?
    () is a list but not a cons yes yes yes
    () is both a list and a symbol yes yes no
    () is self-evaluating yes yes no