Could you give an example of what you mean by this?
807 karma · joined October 19, 2016
Could you give an example of what you mean by this?
I think the problem is that SICP's pedagogical approach (which people confuse with the language Scheme) is, in a way, pretty close to starting with assembler. You spend a lot of time exploring computation (with lambdas as your basis) at a very low level before you graduate to anything that feels "real" or practical.
def find(x, l)
l.each do |y|
return y if y == x
end
end
Where in Lisp you'd use (return-from find x) and in other languages you might pass in a continuation or use a special return value protocol. It's a nice solution for higher order functions that are supposed to feel more language-level.Also, you can pass functions as arguments like in other languages; lambdas behave like you would expect them to.
It's only ever a mild nuisance (in that scenario or for diffs), but the fact that it's trivial to automate seems to make it a no-brainer to me.
And I don't think the function/value split is even the most important namespacing it does with regards to macro hygiene ("functions are less likely to be shadowed" always struck me as a kind of weak argument). Symbols being namespaced under packages is a much more robust solution:
CL-USER> (defmacro m (x) `(car ,x))
M
CL-USER> (defpackage :p)
#<PACKAGE "P">
CL-USER> (in-package :p)
#<COMMON-LISP:PACKAGE "P">
P> (cl:macroexpand '(cl-user::m x))
(COMMON-LISP:CAR X)
CL:CAR isn't accessible in P, so (car x) by itself would signal
UNDEFINED-FUNCTION, but the macro works correctly. Defining a P::CAR function
wouldn't change anything.That isn't just for functions, either, eg if you do:
(defmacro awhen (pred &body body)
`(let ((it ,pred))
(when it ,@body)))
(where you want to leak IT), and then import AWHEN but not IT, you'll get an
error that YOUR-PACKAGE::IT isn't bound to anything when you try to use it; it
doesn't matter that AWHEN-PACKAGE:IT is bound.I don't disagree with anything you said, I just think it made CL macros look flimsier than they are.
What does this mean?
How often do you want to do that? Wouldn't you be more interested in what the variable is called or who it's passed to?
It would say "left paren" and "right paren." It's not terribly hard to keep a stack in your head of how many close parens you're expecting in some context. Manually managing brackets in long or deeply nested forms (in any language) is a pain whether you can see or not. As you said, pseudo-structural editing is a big advantage.
> (I think writing these on a single line makes them written more like they might be spoken)
Screen readers can announce indentation levels.
> Don't worry about superficial tooling support. Solve the language design problems, the hard problems first.
That's mostly what people have been doing for the last most-of-a-century, and it's led to a lot of software being much harder to use for disabled people.
Most Lisps have very little in the way of immutability (except for C-style "undefined behaviour if you modify it"). They tend to be very dynamic, living systems, and making everything mutable is one way of enabling that. Some people favour a functional style, and a functional style is more common in some dialects like Scheme, but it's almost always essentially "imperative code with good support for higher order functions," and most Lisp programs have always mutated state freely.
There are exceptions, but they are exceptions.
I agree that Clojure has a lot of strong points and that s-expressions probably put a lot of people off, but as a Lisp programmer, I was very disappointed in Clojure's debugging/interactive development story (and I've heard that from a lot of others). It feels more like using a typical scripting language compared to the traditional Lisp/Smalltalk experience, and even there, a typical scripting language would at least give useful backtraces. As it stands, I think a decent number of conventional Lisp programmers would also worry about large Clojure programs being unmaintainable unless they're superbly written.
Are they? I don't know if I remember hearing anyone complain about those before.
Python's `eval` also isn't sufficient here. For example, you cannot enter that loop you wrote at the prompt it provides when you run it, even if you write it on one line. You could use `exec`, but then everything would print as `None`.
There's the larger point about forms vs strings and printing readably and such, but this doesn't even provide the same basic functionality that a Python programmer would expect from a REPL.
What isn't strongly typed about Ruby?
There's nothing stopping you from doing
(defmethod add ((x number) (y number)) (+ x y))
(defmethod add ((x string) (y string)) (concatenate 'string x y))
or whatever (multiple dispatch, too), and you could even call it + instead of
ADD if you wanted (but not COMMON-LISP:+, so other code would continue to
work; your packages could import your + instead of the standard one).The spacing between words is better in the "better" version than the "bad" one, but if the point is that we read by words, then breaking up words across lines seems like a much graver sin than bad inter-word spacing.
A limitation that also led to 'Schemer' dropping the R.
C macros are entirely different in use from Lisp macros. C macros are written in a text substitution language that knows virtually nothing about C, whereas Lisp macros are written in Lisp and operate on regular Lisp data structures. Even trivial things are very difficult to get right with C macros, whereas very complicated things are often elegant and simple with Lisp macros. It's an entirely different experience. They're not at all comparable.
C programmers learn to fear macros because C macros have a lot of problems. Few of those carry over to Lisp macros.
That's in chapter 5 :)
Exercise 5.51. Develop a rudimentary implementation of Scheme in C (or some other low-level language of your choice) by translating the explicit-control evaluator of section 5.4 into C. In order to run this code you will need to also provide appropriate storage-allocation routines and other run-time support.
The mechanisms they give you are more than enough, though. Ruby especially favours this kind of thing a lot; how many people really have any idea what's going on (and how many of them beyond "something to do with method_missing and instance_eval") when they write something like:
FactoryBot.define do
factory :foo do
bar { 'baz' }
end
end
I don't think understanding that it's all blocks and method calls wins you
that much.I think this is overstating things, eg, the class `self` belongs to might have defined __getattribute__, __setattr__, etc, so the things you know for 100% certain about that line of code in isolation don't really tell you anything about what it does (self does not definitely have that attribute, and that might not be assignment, at least); most of that you assume based on common sense, conventions, figuring the code is "normal", etc, the same as in Lisp. And that's ignoring a trivial case like if the previous line was `f('''`, where f may or may or not call eval.
If you just want option types that you can use with lists, the most direct way to do that is by encoding None as NIL and Some x as (list x), which still lets "no value" be false, makes "some value" true even for the empty list, and pairs nicely with pattern matching libraries like Optima (which people yearning for option types might also be interested in):
(match maybe-value
((list value) value)
(nil 'not-found))
That seems cleaner to me, too: if you want to use the empty list for "no
value", then why wouldn't you use a list containing the value you want for
"some value"? This also scales to more than 1 optional value, and doesn't
require any special casing for lists, if you're thinking of them as values
like any other; the NIL thing doesn't even come into it. Maybe this sort of
confusion comes from how null works in languages like Java?It might also make sense to represent the absence of a value by just not assigning a value. For example, CLOS instances won't bind their slots to anything by default, so trying to read them will signal UNBOUND-SLOT. You can check if it's bound beforehand with SLOT-BOUNDP, and CLOS does differentiate between a slot not being bound and a slot not existing at all. If you're working on something interactively and UNBOUND-SLOT gets signalled, by default execution will pause, and Lisp will offer strategies for resolving the issue, like entering in a value to use instead for just this call, entering in a value to store in the slot for later use, just aborting the computation, etc. That's a pretty nice experience (and just the tip of the iceberg) that people coming from more static languages might not even think about.
If you're returning a value to someone, maybe it would be best to work like GETHASH does and return two return values, the first either a value pulled from the table or a default, and the second a boolean indicating whether the key was present or whether you got the default. Because Lisp has multiple return values (not like Python or Ruby where you're really just returning a sequence and then destructuring it), callers that are fine with the default and don't care if it was actually present can just use the primary return value, and since the default is provided when you call GETHASH, you can also do nice things like (incf (gethash key hash 0)).
There's other ways too, of course, but it's impossible to say what would be the most appropriate solution without knowing more about the problem. (eq nil '()) returning T is not a bug, so the issue must be higher up the call stack. If that seems like a dealbreaker to someone, they're probably just looking at it in isolation and not thinking in terms of Lisp as a whole.
[f(a, b) for a, b in it.permutations(some_list, r=2)]
And then you could also use itertools.starmap instead of using a comprehension
at all, if you wanted. import itertools as it
[f(a, b) for a, b in it.product(some_list, some_list) if a is not b]