I suppose what I'm asking for is a 1-2 sentence blurb to sell theidea. What's the key advantage for daily work?
I think you have failed to articulate clearly what the problem is.
> Hopefully my paragraph clarifies that a bit. But the key conceptual
> shift is that by enforcing a syntax that moves away from invoking
> methods and move to message passing between objects, you're
> automatically enforcing a more modular approach.
That sounds like wishful thinking to me.
Regards
Antoine.
Sent: Monday, March 18, 2013 11:22 AM
>> We need a concrete example. Let's do `int`s. Currently, a small
> subset of
>> the things we can do with ints includes multiplying, dividing, adding,
>> subtracting, and (formatted) displaying -- how would these operations look
>> with a message passing inteface?
>
> Good. You've chosen perhaps the worst example to demonstrate the
> point, but let's see if I can turn it into an opportunity (haha).
OK, let's use your own example, which you keep reiterating:
>>> 42 >> MyCollectionType #would add the object into your collection: *poof*: no more random syntaxiis for putting things in collections.\
This solves the problem that list.append, set.add, etc. all look completely different. Great.
But what if I want to extend instead of appending? If you say "well, another sequence extends instead of appending", then how do I create lists with lists in them? For that matter, how do I insert at a given position, or replace a slice, or… anything besides the default? The obvious solution is that you pass some kind of wrapper message:
Extender(lst2) >> lst1
Inserter(3, lst2) >> lst1
But now, you're simulating separate methods by using type-switching. That's about as unpythonic as possible. And if you think about it, how can you write Inserter(3, lst2) in this everything-is-a-message syntax? It's going to get really ugly without a lot of sugar. Unless you curry the messages:
lst2 >> 3 >> Inserter >> lst1
Here, you're not sending 3 to lst1, but to "Inserter >> lst1", a lst1-inserter object, and then you're sending lst2 to a lst1-inserter-at-position-3 object. And now you've written Reverse Haskell.
So, why doesn't Smalltalk—which, like Python, is not a type-based language—not have this problem?
I think you've fundamentally misunderstood Kay's design. Smalltalk messages have a structure: a name, and a sequence of parameters. (I'm going to use a fake Smalltalk-like syntax below, to better show off the advantages of Smalltalk and avoid getting diverted into side tracks.) For example:
lst1 extend:lst2
lst1 append:lst2
lst1 insert:lst2 atPosition:3
Notice that this is almost completely isomorphic to the familiar dot syntax, just with different punctuation—space instead of dot, colon instead of comma, and the method name and parameter names intermixed. But only _almost_.
Dot syntax makes it easy to make either the parameter names or the position optional in calls, while Smalltalk-style syntax means you always need both. In other words, you can't
Second, the natural set of first-class values is different.
In Smalltalk, "selectors" are values, while in Python, you can only accomplish the same thing through ugly string-based method lookups:
lst1 perform:(insert:atPosition:) withArgument:lst2 and:3
lst1.getattr('insert')(lst2, 3)
In Smalltalk, "messages" (and/or "invocations") are also values, while in Python, you have to simulate them with lambda/partial/etc.:
message = (insert:lst2 atPosition:3)
message = lambda lst: lst.insert(lst2, 3)
In Python, bound methods and unbound methods are values, while in Smalltalk, you have to simulate them with something like lambda (which I won't show here):
inserter1 = lst1.insert
inserter1(lst2, 3)
inserter2 = list.insert
inserter2(lst1, lst2, 3)
So, there are some minor advantages and disadvantages to each style. But ultimately, there's no real difference between them.
Sent: Monday, March 18, 2013 1:42 PM
>
>> OK, let's use your own example, which you keep reiterating:
>>
>>>>> 42 >> MyCollectionType #would add the object into your
> collection: *poof*: no more random syntaxiis for putting things in
> collections.\
>>
>> This solves the problem that list.append, set.add, etc. all look completely
> different. Great.
>>
>> But what if I want to extend instead of appending?
> All that is very good analysis. However, these data types you talk
> about, I'm gong to argue are explorations from the journey of computer
> *science* which are suboptimal.
I chose these data types because they were your own example.
> But to say suboptimal I have to
> suggest the context in which I'm optimizing. That context is creating
> the idea of a data universe and ecosystem where the ideals of OOP and
> re-usability come out of dreamland and into reality. And the only way
> to do that is to start at the *bottom* and work upwards. That is to
> define the fundamental unit, to re-evalutate the fundamental Object.
> But to figure out that you need also the fundamental communications
> pathway -- how those fundamental objects will interact.
>
> The questions for this data universe can be boiled down to only a few:
> what event necessitates the object/node creation? What is the
> relationship *between* objects?
OK, so "computer science" data types like lists, and integers (as things you can perform both multiplication and addition on), are not fundamental. What is fundamental?
Anyone who knows any mathematical logic can answer this. For example, give me the empty set and a handful of fundamental operations, and I can give you integers (starting with Peano arithmetic) and lists (starting with ordered pairs). Or, give me relations and a handful of fundamental operations. Or…
The problem here is that "handful of fundamental operations". You want just _one_ fundamental binary operation. Well, even that's possible. In fact, you can use the X combinator as both your basic value and your one basic operation.
Even if you _do_ reduce everything to the X combinator, that still doesn't explain how "42 >> mycollection" fails to be ambiguous. There are multiple ways you can model integers and collections that are all perfectly sensible, one of which will make "42 >> mylist" append 42 to the end of the list, one of which will make "42 >> mylist" append to the start, and one of which will make "42 >> mylist" insert the next argument to come in at position 42. Which one of these is "right"? And, if you don't like that type (which, again, was your suggestion), pick any other type that anyone might ever want to deal with.
And this goes back to the same fundamental misconception that I pointed out in the last email, that you skipped over. Smalltalk and Simula, and their descendants, and relational databases, and semantic web initiatives, and so on all have some concept of structure to their messages. This is what allows the same object to do more than one thing. That's why they can model fields and sequences, cars and phones, web services and documents, etc. That's why they're useful.
Finally, if you want to design a whole new language from the ground up, whether on top of the X combinator or ER models or RDF+RIF or whatever, with a completely different model from Python and a completely different syntax… why would you post about it on python-ideas?
> Sent: Monday, March 18, 2013 6:38 PM
> On Mon, Mar 18, 2013 at 5:39 PM, Steven D'Aprano <st...@pearwood.info>
> wrote:
>> - we can no longer distinguish between *adding* something to an unordered
>> collection, and *appending* to an ordered collection;
>
> Your ClassName or __doc__ is supposed to make that clear, because your
> API doesn't. This is the problem I'm referring to when I talk about
> "hyper-personal API's" -- you have to learn the programmer's
> personal language.
What's hyper-personal here? Mutable sequences support effectively the same set of operations in every language. Each language may have different names for these operations, but within a language they're consistent. No matter whose Python code you're looking at, a list has the same append and extend methods—and, for that matter, so does any other class that satisfies the MutableSequence ABC, even any other type that "duck types" as a mutable sequence.
>> - we can no longer distinguish between (for example) *appending* to the end
>> of a list, *extending* a list with a sequence, and *inserting* somewhere
>> inside a list.
>
> Well, these are old data paradigm operations which will go away in my
> view. The very thinking in terms of "lists within lists" is very
> personal and no one else will be able to use whatever you're building.
The idea of non-flat collections is certainly not "personal". Resources contain, or link to, resources, hierarchically or otherwise. Directories contain directories. Research papers reference other resource papers. Web pages link to other web pages. Documents have subdocuments. And so on. Your new paradigm has to account for that, or it's useless.
And meanwhile, the idea that "no one else will be able to use whatever you're building" if you have lists within lists is disproven millions of times per day. I just wrote and ran a script that gets a list of all movies on all channels owned by a certain YouTube user. I had absolutely no trouble using their lists within lists (actually, it's three levels deeper than that), despite the fact that I know nothing about their code, and they've never even heard of me. We just both know JSON, and that's all it takes.
>> - we cannot even distinguish between "put this thing in your
> collection" and
>> "search your collection for this thing", since we're limited
> to a single >>
>> "send message" operator.
>
> Ummm, perhaps you missed something in Python: "search your collection
> for this thing" is done with "in"; i.e. , "item in
> myObject". That
> already is TOWTDI way to do it. Obviously, the first item is already
> handled with the messaging operator.
You've clearly missed something in Python. "item in collection" is, basically, syntactic sugar for "collection.__contains__(item)". And "collection[3]" is "collection.__getitem__(3)". And so on. It's all method calling.
More importantly, you've missed something fundamental about message passing. The whole point of the paradigm is that all operations are messages. That means you can do things like make all messages sequenced and asynchronous (as in Erlang) and get guaranteed-safe concurrency. Or make all message handling dynamic modifiable at runtime (as in Smalltalk) and have hot-swappable code. As soon as you allow end-runs around messaging to access objects directly, none of that works.
And, needless to say, this clearly requires the list type to handle multiple different kinds of methods/messages. You can dismiss the need for extend, insert, etc., but without __contains__, __getitem__, __iter__, etc., a list is completely useless—it's a write-only structure.
At this point, it's starting to feel like that NewsRadio episode where Bill interviews a "business visionary" who has something important to say about the future of computers, then admits that he's never actually used a computer, but thinks they sound neat.
>> * Open a file with that name.
>
> Why does everyone seem to pick the most corner-type cases?
Maybe if you gave a single good example, people wouldn't keep coming up with examples you don't like. But really, almost everyone has picked the two examples you yourself gave: appending to a list, and adding a number to a number. You've complained that those are terrible examples, and haven't given any others, so what can people do but guess what you might possibly have in mind? Meanwhile, I gave you a dozen different examples from a wide range of application areas, and you ignored them completely and went on about list appending.
> But now that you have me thinking on it, I see the file system as
> being composed of namespaces organized in a tree. The Python
> interpreter would access them directly.
Well, because a filesystem contains links and mountpoints and other things, it isn't quite just a tree, but let's accept that.
So, I've got an object representing a directory (a subtree), say, "home", representing "/Users/abarnert". Presumably, "foo >> home" gives me the path "/Users/abarnert/foo". I can send it data to write to that file, or send data from it to read from that file. So far, so good. But how do I select a subdirectory under home? Or get a list of all files and subdirectories under home? Or move a file, or delete it, or change permissions on it, or get its size? Ultimately, there are many things i might want to do with a directory or a file, and therefore it needs multiple methods. Just like a list. Or a number. Or a document, an account, a client connection, a window, a modeled species, etc.
Being able to do multiple things is fundamental to the usefulness of objects. Something that can only do one thing isn't an object, it's a function. In fact, it's a function with a single parameter.
Sent: Monday, March 18, 2013 4:41 PM
> On Mon, Mar 18, 2013 at 2:51 PM, Andrew Barnert <abar...@yahoo.com>
> wrote:
>> Have you even looked at a message-passing language?
>>
>> A Smalltalk "message" is a selector and a sequence of arguments.
> That's what you send around. Newer dynamic-typed message-passing OO and
> actor languages are basically the same as Smalltalk.
>
> Yes, but you have to understand that Alan Kays came with strange ideas
> of some future computer-human symbiosis. So his language design and
> other similar attempts (like php) is rather skewed from that premise
The idea that message passing is fundamentally different from method calling also turned out to be one of those strange ideas, since it only took a couple years to prove that they are theoretically completely isomorphic—and, for that matter, they're both isomorphic to closures.
> And also, despite name-dropping, I'm not trying to create anything
> like that idea of message-passing. I'm talking about something very
> simple, a basic and universal way for objects to communicate.
Message passing is a simple, basic, and universal way for objects to communicate. Everything from dot-syntax method calls to JSON RPC protocols can be modeled as passing messages. But what you're talking about isn't message passing. The idea that messages have names, and reference objects as arguments, is fundamental, and by leaving that out, you're talking about something different.
In effect, your "objects" are just single-parameter functions, and your "messages" are the call operator.
>>> With function or method syntax, you're telling the computer to
>>> "execute something", but that is not the right concepts for
> OOP. You
>>> want the objects to interact with each other and in a high-level
>>> language, the syntax should assist with that.
>>
>> And you have to tell the object _how_ to interact with each other.
>
> This is a different paradigm that what I'm talking about. In the OOP
> of my world, Objects already embody the intelligence of how they are
> going to interact with the outside world, because I put them there.
The paradigm you're talking about is useless. You have lists that know how to append, but don't know how to get/search/iterate. Almost every useful object needs the intelligence to interact with the world in two or more ways.
>> Even with reasonably intelligent animals, you don't just tell two
> animals to interact, except in the rare case where you don't care whether
> they become friends or dinner.
>
> You're model of computer programming is very alien to me. So I don't
> think it will be productive to try to convince you of what I'm
> suggesting, but feel free to continue...
My model of (object-oriented) computer programming is that programming objects model objects which have a variety of behaviors, each of which is triggered by sending a different message. This is pretty much the central definition that everyone who programs or theorizes about programming uses. If you read any textbook, wiki page, journal article, or tutorial, they're all talking about that, or something directly isomorphic to it. If that's alien to you, then object-oriented programming is alien to you.
There are basically two discussions happening here: ADTs (small objects with public contents) vs Encapsulation, and method calls vs message sends. A few responses:
>> or ensuring messages are handled serially per-object,>This happens in either paradigm.
I mean that messages sent from multiple threads are handled serially.
If your entire program is single threaded (as many python programs are) or has a GIL which prevents multiple concurrent method calls, then i guess it isn't a big deal. But in a language that does have pre-emptive multithreading, this basically gives you a non-blocking, guaranteed (because there's no other way to interact with the object other than sending messages) "lock" around each object. Each object basically gets:- its own single-threaded event loop to run its methods- without the performance hit of creating lots and lots of threads- without the overhead of running multiple processes and IPC betweeen the one-thread-per-process event loops (twisted, node.js, etc.) when you want to utilize multiple cores- with minimal overhead over standard method calls (both syntactic and performance)- for zero effort on the part of the programmer
>Yes, and here is where something significant I think will happen.>outside of n x n matrices won't ever get re-used ever. Because there
>Complicated data structures just simply don't get re-used. Python
>allows lists within lists within lists, but any program that uses that
>is no unified data model.I disagree completely. Coming from Java, where every list-within-list has its own special class to represent it, my experience is that is a terrible idea:
- Want to convert from one list-within-list to another list-within-list? Use a list comprehension and be done with it. Want to convert a special InsnList to a special ParameterList? Much more annoying.
It's not like encapsulating the whole thing makes the data struture any less complicated: it just makes it more annoying to do things with, because now I have to learn your way of doing things rather than the standard python-list way of doing things.
In the end, message sends and method calls are basically isomorphic, so much so that in Scala you can transparently convert method calls to message sends under the hood if you prefer that syntax! Unless there's some significant advantage of doing it, it seems to me that the improvement would basically be forcing people to run a regex on their code to convert obj.method(a, b) to obj << (msg, a, b), and then life goes on exactly as it did before.
Sent: Thursday, March 21, 2013 11:08 AM
>I disagree completely. Coming from Java, where every list-within-list has its own special class to represent it, my experience is that is a terrible idea:
This is a side issue, but… that's not the problem with Java lists. After all, Haskell also has a separate type for every list-within-list, yet you can still use comprehensions, map/filter/reduce, etc., and they even work over abstractions rather than just lists (I promise, I won't use the m-word here). Even C++ has std::transform and friends. The terrible idea is having a rigid and non-extensible type system. Haskell (and, to a much lesser extent, C++) avoids that through explicit parameterization; Python (and, to a lesser extent, Smalltalk/ObjC/etc.) avoids it through implicit duck typing; Java forces the programmer to deal with it by writing horrible manual boilerplate.
>In the end, message sends and method calls are basically isomorphic, so much so that in Scala you can transparently convert method calls to message sends under the hood if you prefer that syntax! Unless there's some significant advantage of doing it, it seems to me that the improvement would basically be forcing people to run a regex on their code to convert obj.method(a, b) to obj << (msg, a, b), and then life goes on exactly as it did before.
Here is the main point. Everyone is reading their own ideas into the proposal, because nobody can believe it's as ridiculous as it sounds. The whole key to the proposal is that you _don't_ have messages with parameters like (msg, a, b); a message is just an object. You can do obj1 >> obj2, and that's all you ever need. obj2 knows what to do with obj1, so you don't need to tell it.
Since it's patently obvious that you _do_ need objects with more than one behavior, people assume he _must_ be talking about messages and objects in the sense of Smalltalk/Erlang/Scala/Go/etc.: a message as something that describes an action to take, with arguments. The fact that he's specifically referenced Alan Kay and Smalltalk and papers about this kind of message-sending makes that assumption even harder to avoid. But the assumption is wrong. You can't convert obj.method(a, b) to his syntax, because if you need to, obj isn't a good example of an object.
He's specifically said, multiple times, that if you think there are multiple things obj2 might want to do with obj1, that means you're not imagining the right kinds of objects. Lists, numbers, files, filesystems, servers, documents, hyperlinked webs of documents, GUI windows, database tables, events, animals, cars, ecosystems, user accounts—all of these things are bad examples of objects. And he hasn't given any good examples, no matter how many times he's been asked; the only examples he's given are lists, numbers, and filesystems, all of which he claims are terrible. While his objects are "universal", there are no such things that exist.
Except, of course, for single-argument, void-return functions. They work exactly like what he's describing as "objects". That observation has already been made three times, and he hasn't even responded. He just waits for someone else to come along, read something into his proposal that isn't there, and talk to them until they figure out he's a quack.