Similarly, Python turned me off immediately when I tried to exit the REPL with "exit" and it goes "The way you exit is with control-D." It fucking knew I wanted to exit, and instead lectured me. WTF, Python? Seeya, dick.
That's because there's no magic in the REPL. You're typing nothing but regular Python code.
'exit' is an object. Expressions typed at the REPL are stringified. Thus, when you type 'exit', it gets stringified, and thus you get that message.
'exit' is also a callable, so if you type 'exit()', the REPL will exit.
The same goes for 'help', 'copyright', 'credits', and 'license': they're just objects that get stringified.
Having the REPL work the way you want would mean building magic into it, and magic avoidance is part of the language's culture, so that's not going to happen. Having it print that message is a compromise between keeping things friendly and building magic into the REPL, if you consider printing the result of expressions automatically to be magic.
The Python REPL isn't being a dick, it's being parsimonious and predictable in its behaviour.
Those assumptions that "optimize for developer happiness" 95% of the time make the developer's life hell the other 5% of the time. It happens often enough that "explicit is better than implicit", within reasonable constraints like dynamic typing, is one of Python's core values, and experienced developers usually come to appreciate it.
It's best to design an API that makes what's happening clear without being overly verbose or esoteric, and without making assumptions, especially not dangerous assumptions that may cause you to lose data (exit may close the REPL and cause you to lose your session when you forget that exit is a special word in REPL mode). My personal feelings are that Python has done a better job at this than any other major language. I have major projects in both Ruby and Python and I enjoy the Python ones 100x more because of this.
This document partially explains why; DHH is normalizing shortcuts because it helps him gain new users, even though working around those same shortcuts becomes a major PITA when you need to go a little bit off the beaten path, which happens in one way or another in most real software. There is a way to make these things explicit with only marginally more typing/"effort" (for example, requiring parens to call functions with no arguments, making it explicit and obvious when someone is referencing a variable vs. when they're calling a function), thus avoiding the complexity down the road.
> and experienced developers usually come to appreciate it.
I find statements like this a bit condescending. It's like when Java developers say 'Oh, that's nice, but I work on big applications." "Obviously you don't get it because you're not experienced. Experienced developers get it."
I'm sure that wasn't your intent. But it still irks me a bit.
I've been writing software for a very long time, in many languages and paradigms over the years. I believe that qualifies me as an experienced developer. My years of experience tell me that if I only experience pain 5% of the time, then the 95% of the time I don't experience the tools I use making me do extra work more than makes up for it. :)
>I'm sure that wasn't your intent. But it still irks me a bit.
Correct. I meant it inclusively, like most of the Python community is either experienced developers who've worked in a lot of languages and have sought refuge in the sanity of Python or the apprentices of such developers. Python doesn't have a figure like DHH to give it sex appeal, so not many new people use it.
There are definitely experienced developers who have not yet had occasion to seriously enjoy and appreciate Python.
>My years of experience tell me that if I only experience pain 5% of the time, then the 95% of the time I don't experience the tools I use making me do extra work more than makes up for it. :)
I meant this as a count of the number of issues, not the amount of time it takes to resolve them. That 5% of problems caused by non-obvious implicit magical behavior usually take an inordinate amount of time to debug and solve, and then the workarounds are usually disgustingly ugly because the framework had never conceived that someone might have a valid reason to circumvent their magic. Even worse, this locked-down, "looking inside will void your warranty" attitude (which Rails often calls "convention over configuration") frequently means that the workaround must be somewhat pervasive and ugly up your code not just in one place, but in several places to really resolve the problem.
Experienced developers may not encounter this often if they don't use a lot of magical APIs that promote the systemic ambiguity of "do what I mean".
> Even worse, this locked-down, "looking inside will void your warranty" attitude (which Rails often calls "convention over configuration") frequently means that the workaround must be somewhat pervasive and ugly up your code not just in one place, but in several places to really resolve the problem.
In 2005, when nobody used Rails, as a complete beginner to Ruby, I was able to write a patch to improve the Oracle database adapter. Somehow, despite all the magic, it took me very little time to find what to change and submit a patch. As a complete newbie to the language and platform.
Rails 2 and below revolved around a lot of hacks. Rails 3 removed many of those. Rails 4 even more so. Check out the book "Crafting Rails 4 Applications" to see how easy it is to hook into places in the framework to get what you need. (Disclosure: I'm the editor of that book.)
The argument you make about "voiding the warranty" is an argument I hear from people who have touched Rails for a project and hated it for one reason or another. It's not a sensible argument.
Remember, for centuries, people used "magic" to explain away something they didn't understand. :)
> Yet the code behind the REPL knows exactly what I was attempting to do and tells me that.
Not really – all the code knows is to stringify objects, and it saw the object called `exit`, for which the stringified version is that error message. But nowhere in that flow does the code know how call anything that could exit the REPL.
/doesn't use Python regularly and thinks the smart move would be to make the REPL understand certain things as commands instead of normal Python code.
Well, the __repr__ method on the 'exit' object is getting called. So that method could actually call sys.exit() and cause repl to exit. But it would be very rude and strange behaviour for calling a __repr__ method to exit the interpreter, or, indeed, to do anything other than return a string representation of the object.
What someone more clever than me ought to do is write up a huge investigation of an obscure bug caused by exit.__repr__() calling sys.exit(), and then only at the end comment that it's not real, but could've been if Python went down this road.
If the REPL initiates an exit via throwing an exception (eg.g new QuitException ) and having the top level of the stack catch it then handle the termination, then you could have the stringification of exit throw that exception too.
It's not really magic at that point. It's not really 'magic' at that point to me.
Granted some people might say its surprising for stringifying an object to call sys.exit() or throw an error that'll cause exiting.... But within the context of the REPL its not surprising.
Whatever method you take, whether it's a special case after reading a line, or some other weird hack, you're adding magic.
With Ruby, this isn't a big deal because if `exit` is a function, then typing `exit` will call it, and you need to use the unary `&` to bypass this and have the callable treated the same way that Python does (which is roughly equivalent to a Ruby Proc).
Ruby doesn't require the hack, but Python would, for better or worse.
But also, how is bundling that explanation into the stringified version of the "exit" object not as magicky as Ruby putting an "exit" method on Kernel which is in the root namespace of the REPL and exits the current process (with an optional exit code)?
Slight correction, `exit` in IRB is actually not the same as `Kernel.exit` (as IRB actually allow IRB sessions to be nested, using `Kernel.exit` would quit the whole program rather than just the current session). It is actually an alias of `IRB::ExtendCommandBundle#irb_exit` that throws `IRB_EXIT` to stop the eval loop of the current session and allow some pre-exit hooks to be assigned to it.
Ruby's "exit" doesn't have to be as magicky owing to how simply typing "exit" in Ruby is a valid way to call a function with no arguments. The problem, if you can even call it that, is that Python doesn't have the separation between functions/methods on the one hand and procs on the other.
That is a critical difference that needs to be kept in mind.
Edit: grammar fixes. I didn't reread by comment before posting. :-/
Python 2.7.10 (default, Sep 8 2015, 17:20:17)
[GCC 5.1.1 20150618 (Red Hat 5.1.1-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> exit = "I'd like to assign a value to this non-reserved word!"
>>> exit
"I'd like to assign a value to this non-reserved word!"
1) Did they fix the thing where you had to use __self__ practically everywhere? Double underscored variable names also look like crap, especially when they're everywhere. It looks like a really shitty design flaw, having to use __self__ this and __self__ that everywhere. (This situation may have been ameliorated since I last looked at the language.)
2) Not everyone likes significant whitespace. But moreover...
3) If you code for long enough, you'll eventually realize that Ruby, Python, C++, etc. etc. are all fundamentally flawed due to OO making it too easy to write spaghetti-dependency code with mutable state everywhere, which means EVERY OO CODEBASE NO MATTER WHAT eventually becomes an unmanageable bugridden long-test-runtime complexity tarpit.
They should stop calling it "technical debt" and start calling it "object-oriented debt" ;)
So you eventually convert to the Functional Programming™ religion. ;) And that is the real reason not to use Python (or Ruby or Java or C), IMHO. But don't take my word for it, here's John Carmack basically saying that everyone should use functional paradigms (even in OO languages) if at all humanly possible: http://gamasutra.com/view/news/169296/Indepth_Functional_pro...
1) __self__? I don't think I have ever seen that. Are you combining two problems? All metamethods are quoted with dunderscores, and the first parameter of each method is "self", both of which annoy some people...
2) That is a reasonable choice. I like expressing myself once, instead of repeating myself in both whitespace and brackets or whatever, but I get the argument for autoformatting after copy/paste. Of course, lua does this the best, by having neither significant whitespace nor semicolons, and just figuring out where statements end from the grammar.
3) Objects are wholly optional, and can mostly be avoided. the functools and itertools libraries go a long way to bring the functional religion to python.
You should probably be careful there -- shared mutable state is the problem, and as Rust demonstrates, attacking the "shared" part can be just as effective (if not more so) than attacking the "mutable" part.
Both come with tradeoffs, but Rust's borrow checker seems, anecdotally, to be easier for people to understand than the category-theory word salad that litters most strictly-immutable FP language discussions.
I have seen showstopper, up-all-night bugs with BOTH shared mutable state (see: the reason it is nearly impossible to reduce a certain Rails app's test suite runtime below about a half hour without significant rewriting) AND single threaded mutability bugs (in one case resulting in me taking a month to fix a login bug that nobody else at the company was able to find out in over a year... It was a hash mutation deep inside a middleware affecting auth cookies)
A simple example of a single threaded mutability bug is passing an object by reference to a method which then (either intentionally or unintentionally) mutates the object, to the complete ignorance of the caller up in the call stack. The only thing stopping that is literally, programmer skill. No language constructs. At least in all the OO langs I ever worked with.
It's self, not __self__ (it has never been __self__). Double underscore names are used for special methods, which are typed relatively rarely, the most common probably being __init__.
Most Python code does not involve the creation of classes, so I think you're wrong to associate it with true OO message-passing languages such as Ruby and Java.
I really like IPython for that reason: it does what I expect. There's a bunch of magic, but they explicitly call it out as such. It's been built to handle that frustration.
I'm a Ruby guy that has tried Python several times. My takeaway is always that Python is beautiful and wonderful, but it just has all of these tiny little annoyances that build up and make me rage quit. Exiting out of the REPL is one of these things. I guess I spend so much time working with, and get so much work done in, IRB that anything that trips me up in a REPL just gets under my skin.
I really, really want to like Python, but I just can't seem to stay with it long enough to work through all of the "weird" stuff. :/
I think they did something to fix it, but back when I was investigating Python, it was useful to pass references to yourself to everything for some reason, so every method passed like __self__ to itself, it seemed like __self__'s were everywhere. Coupled with how fucking ugly (opinion, of course) double-underscored variable names are, it just did not give me a great impression.
You're wrong and you confuse two different things.
So called "magic" methods, which are called by Python in various circumstances, are surrounded by double underscores. One of such methods is __init__, which works as an initializer for newly created objects.
The other thing is that all methods, unless otherwise specified, take a pointer to the current object as a first argument. So when defining an initializer that takes no arguments from the user you write:
def __init__(self): # etc.
I think only __init__ and __name__ are widely used in Python, all the other such methods are very special purpose and very rarely used outside of deep library code.
BTW: Being "ugly" or not is not the best characteristic you can base your opinion of a language on.
'exit' is an object. Expressions typed at the REPL are stringified. Thus, when you type 'exit', it gets stringified, and thus you get that message.
'exit' is also a callable, so if you type 'exit()', the REPL will exit.
The same goes for 'help', 'copyright', 'credits', and 'license': they're just objects that get stringified.
Having the REPL work the way you want would mean building magic into it, and magic avoidance is part of the language's culture, so that's not going to happen. Having it print that message is a compromise between keeping things friendly and building magic into the REPL, if you consider printing the result of expressions automatically to be magic.
The Python REPL isn't being a dick, it's being parsimonious and predictable in its behaviour.