8 ms·
Could you tell me how to eliminate the notion of object identity from Lisp when it's unwanted? (FWIW, “just don't use eq” isn't good enough advice, because you
by catnaroek 10y ago
Could you tell me how to eliminate the notion of object identity from Lisp when it's unwanted? (FWIW, “just don't use eq” isn't good enough advice, because you can just mutate one of two supposedly “equal” objects to observe the difference between them.) This is crucial to make functional programming work.
- qwertyuiop924 10y ago>Could you tell me how to eliminate the notion of object identity from Lisp when it's unwanted? So by "object identity," you mean doing equality comparisons with pointers instead of actually value equality. Just don't use eq. Use equal instead. >FWIW, “just don't use eq” isn't good enough advice, because you can just mutate one of two supposedly “equal” objects to observe the difference between them Yeah, that's true of every programming language in existance. What, do you want every equal object to be mutated, along with the one you mutated? That's just crazy talk. >This is crucial to make functional programming work. Well, if you're doing FP, than you aren't mutating values anyway. So just don't use eq. At this point, your question utterly baffles me. Come back with a better explanation, or some examples of what you want, and I'll try to give you a better answer.
- catnaroek 10y ago> So by "object identity," you mean doing equality comparisons with pointers instead of actually value equality. Just don't use eq. Use equal instead. The problem goes further than that. “equal” doesn't really mean equal - it only means equal until the next mutation. Not very helpful in a language with unrestricted mutation. > Yeah, that's true of every programming language in existance. It isn't true in ML or Haskell. In those languages, mutation is strictly opt-in, on a per-type basis. When you don't use mutation, equality is by value. When you use mutation, equality is by object identity. You can't “accidentally distinguish” between two copies of the same immutable value. > What, do you want every equal object to be mutated, along with the one you mutated? That's just crazy talk. I want to distinguish between objects meant to be mutated and values not meant to. > Well, if you're doing FP, than you aren't mutating values anyway. So just don't use eq. If I'm doing functional programming, I want to mutate as little as possible, but certain things require mutation to be done efficiently. What I want is the language to help me distinguish between functional values and imperative objects. (0) ML: If you attempt to mutate a value, you get a compile-time error. (1) Racket: If you attempt to mutate a value, you get a runtime error. Pretty sensible in a dynamic language. (2) Common Lisp: Only primitives are values, and mutating anything else is permitted.
- lispm 10y ago> Pretty sensible in a dynamic language. For a dynamic language I would expect to be able to change objects, in many ways. Otherwise it would not be 'dynamic', but 'static'. > (2) Common Lisp: Only primitives are values, and mutating anything else is permitted. But not necessarily possible. In this Common Lisp example there is no way to change the closure bindings. CL-USER 107 > (let ((f (let ((a 10) (b 10)) (lambda (x) (+ x a b))))) (* (funcall f 1) (funcall f 2))) 462
- catnaroek 10y ago> For a dynamic language I would expect to be able to change objects, in many ways. That objects must support mutation was never in question. All general-purpose languages have mutable objects. The point is whether you also want to have compound values. I think the answer should be “yes”. > In this Common Lisp example there is no way to change the closure bindings. Point taken. But I hope you aren't suggesting me to Church-encode compound values. Even ignoring the usability issues of Church encoding, there's still the problem that Common Lisp will treat two otherwise extensionally equal functions as different objects. :-p
- qwertyuiop924 10y ago...but, the functions are side-effectless, and guaranteed not to change, so you can just equal? the results. Basically, you can use function as const.
- deleted 10y ago[deleted]
- catnaroek 10y ago> ...but, the functions are side-effectless, and guaranteed not to change, so you can just equal? the results. Just because (f 1) and (g 1) are equal, it doesn't automatically follow that f and g are equal. Functions shouldn't be comparable for equality at runtime, because extensional equality of functions is undecidable.
- junke 10y agoAre there people saying that you can't implement Common Lisp in OCaml/Haskell, with heterogeneous structures, dynamically typed and so on? You certainly can, but that's not an easy task. If your language does not support natively the set of rules you really like to enforce, you can still work in a style you like provided you establishes conventions and abstractions around it. So if you were actually interested in developing with, say, purely functional collections in Common Lisp, you would use the FSET[0] library: (equalp (fset:bag 1 2) (fset:bag 1 2)) T (equalp (fset:bag 1 2) (fset:bag 2 1)) T That's exactly the same approach that people use when they do "C with classes", by reifying the abstraction into the host language. Unsurprisingly, data-structures in FSET are implemented as Common Lisp structures, which allows having read-only slots as well as type specifiers. Since we only want to have read-only slots, let's define a wrapper around defstruct (http://pastebin.com/30D6BnRg http://pastebin.com/30D6BnRg): (deftype tree () '(or node null leaf)) (deft leaf data) (deft node (left nil :type tree) (right nil :type tree)) (equalp (node (node (leaf 3) (leaf 4)) nil) (node (node (leaf 3) (leaf 4)) nil)) => T (compile nil (lambda () (setf (data (leaf 3)) 0))) ; compilation unit finished ; Undefined function: ; (SETF DATA) ; caught 1 STYLE-WARNING condition (compile nil (lambda () (node "strange" "node"))) ; caught WARNING: ; Constant ; "strange" conflicts with its asserted type ; (OR COMMON-LISP-USER::NODE NULL COMMON-LISP-USER::LEAF). ; See also: ; The SBCL Manual, Node "Handling of Types" The above messages are caught while compiling the functions, not when calling them. The language specification does not give you another way to mutate structure slots other than opt-in accessors. Granted, your implementation is probably smart enough to let you mutate it with (SETF SLOT-VALUE), but as a CL developer, you know that its bad to use it and that you should rely on accessors provided by the API. The advantage of Lisp at this point is that you can wrap your abstraction so tightly that it is indistinguishable from core features. But maybe that's not enough for you, because there are not enough checks and you have your own idea of how things should work and you think you should do more compilation and less interpretation. The compiler is available right here too, so you can even use it from your own compiler (during macroexpansion if you need) and do the analyzes you like. Or, you can even output a completely different executable file. Then you code a compiler in your new language because that's what grown-up languages do. So, in order to do ML in Lisp, you develop your own DSL, an interpreter and later you bootstrap a compiler which is then self-sufficient to exist alone. That's roughly what happened decades ago: "Edinburgh LCF, including the ML interpreter, was implemented in Lisp"[1][2]. ACL2, an applicative, side-effect free subset of Common Lisp used for first-order theorem proving (Boyer-Moore), shares the same approach. But the language that exists now, even though it does perfectly what it was designed for, is not Lisp anymore, and can't do exactly the things we actually like to do in Common Lisp. [0] https://common-lisp.net/project/fset/Site/index.html https://common-lisp.net/project/fset/Site/index.html [1] https://ocaml.org/learn/history.html https://ocaml.org/learn/history.html [2] https://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.pdf https://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.pdf