Hacker News new | ask | show | jobs
by albedoa 937 days ago
For those of us who are unfamiliar with Lisps, can you expand on how they break referential transparency (and how Standard ML contrasts in that regard)?
2 comments

They don’t.

Or at least not inherently, if by “lisp” one is primarily referring to s-expressions.

He is probably talking about namespaces. In common lisp, for example,

   (a a)
calls a function 'a' on a variable 'a'. Lisp knows this because the first thing that comes after the left paren is a function
more importantly there are functions (using scheme as an example) like set! and set-cdr! that mutate existing values and totally break referential transparency.

this isn't just user facing - for example let* kind of depends on creating bindings up front so they work across clauses, and then mutating them afterwards

Why does `let*` need to have mutation? It can be nested `let`s.
let* permits expressions on the right refer to arbitrary other symbols bound by the let*. in particular it allows for construction of recursive lambdas that may not be linearlizable.
> let* permits expressions on the right refer to arbitrary other symbols bound by the let*

In what language? I just checked Elisp, SBCL, and Guile, and they all error out if you refer to a variable not previously defined by a left-to-right traversal of the varlist:

    (let* ((a (+ b 1)) (b 1)) a)
Edit: This doesn't work either:

    (let* ((a (lambda () (+ b 1))) (b 1)) (funcall a)) ; (funcall a) -> (a) for Schemes
Anyway, as far as I'm seeing it's perfectly possible to implement let* consistent with the above behavior as a macro without mutation as such:

    (define-macro (let* bindings &rest body)
      (if (null? bindings)
        `(progn ,@body)
        `(let (,(car bindings))
           (let* ,(cdr bindings)
             ,@body))))
You're thinking of letrec.
Ah, that does look to be the case. I didn't know about that one.

    Signature
    (letrec BINDERS &rest BODY)
    
    Documentation
    Bind variables according to BINDERS then eval BODY.
    
    The value of the last form in BODY is returned.
    Each element of BINDERS is a list (SYMBOL VALUEFORM) that binds
    SYMBOL to the value of VALUEFORM.
    
    The main difference between this macro and let/let* is that
    all symbols are bound before any of the VALUEFORMs are evalled.
Lisp allows you to mutate, you can certainly write non-mutating code in lisp. Why do you think you need to use mutation with let*? let* is just a sequential let

https://www.lispworks.com/documentation/lw70/CLHS/Body/s_let...