I know people are tired of the "lisp/smalltalk did it better" but what features of python are not possible (or hard) in CL[OS] ?
ps: how many CL shops are out there ? I'd work near free just to try a CL team once.
I know people are tired of the "lisp/smalltalk did it better" but what features of python are not possible (or hard) in CL[OS] ?
ps: how many CL shops are out there ? I'd work near free just to try a CL team once.
The way methods are ‘attached’ to classes gives you a natural form of type-directed name lookup. CL generics have the advantage that you can define your own methods on existing classes while naming the methods in your own package so they don't conflict, but also have a curse of inconvenience along the way where importing a class doesn't naturally pull in everything associated with it, and you wind up writing the class name again and again when dealing with fields of an object. with-slots et al. are poor substitutes. (In an experimental sublanguage at one point I actually had local variables with object type declarations implicitly look up the class using the MOP and symbol-macrolet every available var.slot combination within the scope as a brute hack around the most common desirable case.)
Python's short infix/prefix operators are naturally generic, since they're implemented as method calls. In CL there's the generic-cl extension, but I haven't seen it have that much uptake… in particular, any library code that isn't explicitly aware of it won't use it ‘naturally’ on foreign objects, which could be good or bad.
That shades into the very-concrete type system that CL starts out with, where any attempt at ad-hoc polymorphic interop is a disaster unless everyone already agrees on what methods to use. I can't make a thing that acts like a hash table but uses a different implementation underneath, then pass it to something that expects to be able to gethash on it. I especially seem to get bitten by this in cases where alists are the expected way of representing key-value maps: there's no way to extricate yourself from the linear search without rewriting every piece of code that touches it, there's often an implicit contract that you don't want duplicate keys but it's easy to violate by accident and create bad behavior down the line, and so on. By comparison, Java collections in particular got this very right in terms of decoupling intention from implementation, and Python does basically the same thing but with a looser set of ‘expected’ methods.
By default, Python objects have a ‘purely’ dynamic set of properties, rather than the fixed slots CLOS imputes on an object via its class. Indeed the class-level property one can set in Python to constrain this for possible performance gains is called __slots__.
CLOS already offered AOP, which you can use to control such calls.
https://lispcookbook.github.io/cl-cookbook/clos.html#dispatc...
class KnowItAll(object):
def __getattr__(self, attr):
return lambda: "yes, I know how to " + attr
We then have: >>> k = KnowItAll()
>>> k.reveal()
'yes, I know how to reveal'
>>> k.transfigure()
'yes, I know how to transfigure'
>>> k.make_sandwiches()
'yes, I know how to make_sandwiches'
Ruby does this with method_missing instead, which is where I'm most used to it happening (and I think it's used a lot more in Ruby than in Python owing to the language-culture's higher tolerance for magic). Smalltalk used doesNotUnderstand, IIRC. One of the key secondary results of this is that you can do things like https://paste.ee/p/tUaRP, which is a toy “Tracer” class which intercepts, prints, and forwards method calls and attribute accesses (ignoring some edge cases).If we were in CLOS, and started with:
;;; widget.lisp
(defclass widget () ((radius :initarg :radius)))
(defmethod grow ((w widget)) (incf (slot-value w 'radius)))
What I would expect for the equivalent is that, given: ;;; tracer.lisp
(declaim (ftype (function (t) t) make-tracer))
(defun make-tracer (object) ...)
;; ... further code goes here ...
Somewhere else, we can do: ;;; fiddle-with-widgets.lisp
(let* ((w (make-instance 'widget :radius 3))
(w* (make-tracer w)))
;; ???
(grow w*))
Can you add code to tracer.lisp, without specific reference to anything from widget.lisp, such that this has the effect of (grow w) but prints what it's doing? Note also that my use of slot-value above is very deliberately a ‘raw’ access. I'm here completely ignoring the “make up entirely new methods on the fly as needed” part that method_missing also gets used for, which is even more impossible in CLOS given that it would require intercepting, what, all symbol lookups…In CLOS, the class doesn't ‘own’ the method, it provides a type for dispatching on, so there's no way to do “give me some control over every generic function so long as the first arg is of ‘my’ type”. Which is a reasonable model, but means you can't do the same thing. Indeed the flip side is that in the Smalltalk-like model, generics are not reified, and methods that are specializations of the ‘same’ thing have no ‘real’ identity to them, so you can't do a type-ignoring :around method for an ‘entire generic’. (Often there will be a superclass to attach to instead, but it's considered dangerous “monkey patching” to mess around with someone else's class hierarchy like that, and in the case of more abstract interfaces there's nothing.)
Does that make sense?
(defclass tracer () ((actual :initarg :actual)))
(defun make-tracer (object)
(make-instance 'tracer :actual object))
;; But please don't.
(defmethod no-applicable-method :around (gf &rest args)
(if (typep (car args) 'tracer)
(let* ((tracer (car args))
(args* (cdr args))
(actual (slot-value tracer 'actual)))
(format t "Calling ~S on ~S" gf (cons actual args*))
(apply gf actual args*))
(apply #'call-next-method gf args)))
You can't do this with a real specialization on no-applicable-method, incidentally, because the first arg isn't special enough, it's just folded into the &rest. And that, I'm pretty sure, means this doesn't coexist with other uses of no-applicable-method properly… and you still can't do on-the-fly method names that aren't attached to a generic, and so on, but this does sort of account for the object forwarder case (and in fact you could extend it to allow tracers on more of the arguments!). It does, I expect, remain extremely unidiomatic by comparison.All I can think of is what a mistake those features were in python. "Fancy tricks" are generally the author trying to be clever (in the Kernighan sense) and ends up obfuscating the result. Not saying it works this way in CL, I don't have the experience to make the call, but in Python it was (and probably still is) prevalent.
> the author trying to be clever (in the Kernighan sense)
I know that Kernighan is the author of the C book, but its been a while since I skimmed it.
I like python but I'm a bit fed up with the mobthink (how surprising).
Programming is something interesting and fun they are going to do for a few years while young, until (pick one) {their band takes off, someone funds their startup idea and they hire others to do the programming while they generate genius ideas and run the business, they get promoted to a high paying executive position that involved management and architecture and others do the coding, their podcast becomes a hit and they can live off that, the small company they work at IPOs or gets bought and they make enough to retire at 30, etc).
So they learn a fairly easy language that has lots of libraries that cover most things you do in a routine developer job.
...and before they know it they are 50-60 and still writing a lot of code, and realizing that if they had known they would still be doing this 30-40 years later they would have been better off if they had learned and used and gotten good with some of the languages that have a reputation of being very productive but hard to learn.
I'd also add spreadsheets and database to that. At one point I was the database guy at work, because no one else was available. I learned enough SQL to get by, but was in no way a database expert. Heck, we had to pick job titles at one point that described what we did to have on the business cards the company was giving us, and I put down "Database Roustabout" [1], which should give you an idea of where I stood. That was 20 years and I'm still the database guy at work. It would have been a lot better if sometime early on I had said "I'm going to become really good at SQL even though I'm sure someone else will become database guy in a year or so".
[1] Roustabout. NOUN. An unskilled or casual laborer. (North American) A circus laborer.
This manifested in a few spots. The first is, yes, s-expressions. Yes, yes, I know, s-expressions are integral to the power of lisp, and they're really not hard to read once you get used to them. All of that is beside the point. The reality I saw on the ground, when teaching people to program, is that even people who have no prior programming experience whatsoever, and therefore no preconceptions to get over, have a harder time grasping s-expressions than they do algol-style syntax. I don't know why. I didn't have the same problem myself. But it's a real phenomenon that I struggled to help people through on a regular basis, and the lisp community's defensiveness about it is not going to make it go away.
Arbitrary-seeming names with zero mnemonic value is another problem. Car, cdr, progn, etc. - it takes a special kind of personality to not be put off by this sort of stuff. Not everyone has that kind of personality. Not everyone should have that kind of personality.
Finally, all the hair-splitty (at least to a newcomer) distinctions to contend with. =, eq, eql, equal, and equalp, or let, let* and letrec. Sure, there are reasons for these distinctions. But a language that can get by without quite so many of them is going to be a lot more attractive to newcomers. Even if it comes at the cost of footguns, if they're unlikey to be discovered until later.
For someone to to even make the determination that "Lisp did my better" they must
A. Have a comprehensive knowledge of both Python and Lisp.
B. Have some understanding of the history of the languages.
C. Understand the problem on an intrinsic, fundamental level that enables them to evaluate which approach is better.
D. Generalize the problem to a broader scope to demonstrate why one language is better on a broader level.
E. Understand every other language ever used so when they show why Lisp does it better, they can defend themselves when someone comes up and tells them, "actually, Pascal does this even better yet."
Furthermore, it doesn't really matter what language did it better. The point of most talks is simply to demonstrate how to solve some problem in a certain language. If every talk started with "you can do this in Python, and I'll show you how, but you should probably just switch to Lisp because it does it better," that isn't very helpful.
It's more about the 'wheel reinvention' syndrome that creates fatigue in me.