Live data from Hacker News

A late-night rant about OOP and parametric dispatch

devblog.avdi.org

21–30 of 33 posts

Re: A late-night rant about OOP and parametric dispatch

#22
post #19
post #9

Consider this tree: dog bark softly loudly run fast slow cat meow purr run Let's call this the coordinate-system of your API (I've extended it a little hopefully to make it clearer). It's a small coordinate-system because it doesn't take any strings, but whatever. 'loudly' only makes sense in the context of 'bark', and 'bark' only makes sense on the context of 'dog'. That list of nodes is a coordinate that identifies…

This is good intuition. I tend to use this kind of idea in code these days. Here's an example: ($define! tree ($branch (dog ($branch (bark ($branch (loudly ($lambda () "WOOF!")) (softly ($lambda () "woof")))) (run ($branch (fast ()) (slow ()))))) (cat ($branch (meow ($lambda () "meow!")) (purr ()) (run ()))))) ($walk tree ($walk dog ($walk bark (loudly))) => "WOOF!" If one attempted to just say (loudly), an error wou…

Cool beans, sparkie. I'm working on something involving this shape (which is like a set of related call chains). The ability to call "loudly" by itself is a convenience only, a kind of syntactic sugar. I'm sure you could easily write the function that takes this tree, the string 'loudly' and emits '($walk tree ($walk dog ($walk bark (loudly)))'. :)

Re: A late-night rant about OOP and parametric dispatch

#23
post #19

Earlier quoted context omitted.

This is good intuition. I tend to use this kind of idea in code these days. Here's an example: ($define! tree ($branch (dog ($branch (bark ($branch (loudly ($lambda () "WOOF!")) (softly ($lambda () "woof")))) (run ($branch (fast ()) (slow ()))))) (cat ($branch (meow ($lambda () "meow!")) (purr ()) (run ()))))) ($walk tree ($walk dog ($walk bark (loudly))) => "WOOF!" If one attempted to just say (loudly), an error wou…

Cool beans, sparkie. I'm working on something involving this shape (which is like a set of related call chains). The ability to call "loudly" by itself is a convenience only, a kind of syntactic sugar. I'm sure you could easily write the function that takes this tree, the string 'loudly' and emits '($walk tree ($walk dog ($walk bark (loudly)))'. :)

There's certainly a few ways you could do that in Kernel, but I think doing this is less interesting than having to explicitly specify the environments to enter to find loudly to begin with. I see other potential problems with just specifying loudly alone which we'd look to avoid up-front, or explicitly handle as part of the tree.

So for example if the cat were to become able to meow loudly, there would no longer be just one path into loudly, but we'd have two. Typically, we'd either make loudly a generic and specify (or infer) the type were referring to, or we'd have a dynamically-dispatched loudly which looks at say, the first argument and decides which one to execute. Both the these are trivial to implement as environments. However, the more interesting gain Kernel gives is that there is no way built into the language to enumerate the bindings in an environment (a deliberate omission).

We can walk through the tree as given in this example (using car and cdr), but if there were some binding "(mouse the-mouse)" added to the tree, where the-mouse is already an environment, and not a list of instructions to build a new environment, then we cannot walk through its sub-tree, which may contain bindings such as "loudly".

We can explicitly call ($binds? the-mouse loudly) to test this, iff "the-mouse" is an environment. This works by basically attempting to retreive the binding loudly and capturing the error thrown if it doesn't exist in the binding. In practice, we'd have a more interesting type for mouse, which contains an environment, but encapsulated (via make-encapsulation-type), with only predefined ways of accessing the bindings of the environment.

With this setup, a binding in an environment behaves much like a capability. By knowing the name of a binding, you have the capability to use it. Proper use of the environment and encapsulation types gives us the flexibility to tightly constrain who can see "loudly".

Re: A late-night rant about OOP and parametric dispatch

#24
post #23

Earlier quoted context omitted.

Cool beans, sparkie. I'm working on something involving this shape (which is like a set of related call chains). The ability to call "loudly" by itself is a convenience only, a kind of syntactic sugar. I'm sure you could easily write the function that takes this tree, the string 'loudly' and emits '($walk tree ($walk dog ($walk bark (loudly)))'. :)

There's certainly a few ways you could do that in Kernel, but I think doing this is less interesting than having to explicitly specify the environments to enter to find loudly to begin with. I see other potential problems with just specifying loudly alone which we'd look to avoid up-front, or explicitly handle as part of the tree. So for example if the cat were to become able to meow loudly, there would no longer be…

>However, the more interesting gain Kernel gives is that there is no way built into the language to enumerate the bindings in an environment (a deliberate omission).

Why not throw an error, or perhaps return a curry that takes an integer picking between multiple 'loudly' options? Its true that such 'do-special' would need global read access to the entire program, which is perhaps what you want to avoid?

Re: A late-night rant about OOP and parametric dispatch

#25
post #23

Earlier quoted context omitted.

There's certainly a few ways you could do that in Kernel, but I think doing this is less interesting than having to explicitly specify the environments to enter to find loudly to begin with. I see other potential problems with just specifying loudly alone which we'd look to avoid up-front, or explicitly handle as part of the tree. So for example if the cat were to become able to meow loudly, there would no longer be…

>However, the more interesting gain Kernel gives is that there is no way built into the language to enumerate the bindings in an environment (a deliberate omission). Why not throw an error, or perhaps return a curry that takes an integer picking between multiple 'loudly' options? Its true that such 'do-special' would need global read access to the entire program, which is perhaps what you want to avoid?

Global access to the entire problem is definitely what we want to avoid. Encapsulation is a useful tool for maintaning invariants, controlling access to data and so forth. We don't want to give it up, and we may not even be able to give it up to begin with.

One way to consider an environment is that it may be a physical machine with a set of bindings, variables and processes on it, where some of the bindings point to other environments - ie, other machines.

Then if you're a user of the tree, and you want to invoke loudly, there exists at least one other machine, dog, between yourself and bark. You obviously don't know what is on the bark machine because the dog mediates your access to it. You don't really have any access to it other than by sending a message to dog which asks it to send a message to bark.

So the number of "loudly" options in such a network (ie, the internet) is potentially unbounded. The dog machine might know, by virtue of asking the machines it is directly connected to, whether the binding loudly exists, and it can chose to expose either the binding, or another message named loudly which discriminates between multiple loudly options if the other machines connected to it (aka, dynamic dispatch).

In the example above, replace "$branch" with "$spawn" and "$walk" with "$send", and you effectively have the actor model, which is more like the OOP imagined by Kay and others. The fun part here would be to implement $spawn and $send though.

Re: A late-night rant about OOP and parametric dispatch

#26
post #25

Earlier quoted context omitted.

>However, the more interesting gain Kernel gives is that there is no way built into the language to enumerate the bindings in an environment (a deliberate omission). Why not throw an error, or perhaps return a curry that takes an integer picking between multiple 'loudly' options? Its true that such 'do-special' would need global read access to the entire program, which is perhaps what you want to avoid?

Global access to the entire problem is definitely what we want to avoid. Encapsulation is a useful tool for maintaning invariants, controlling access to data and so forth. We don't want to give it up, and we may not even be able to give it up to begin with. One way to consider an environment is that it may be a physical machine with a set of bindings, variables and processes on it, where some of the bindings point to…

Ah, you inspire the thought that we don't necessarily know what the valid inputs are to a distributed program! But realize that this problem will bite you even higher up the food chain (so to speak) because what if there is more than one dog node? What does your program do in that case?

Re: A late-night rant about OOP and parametric dispatch

#27
post #25

Earlier quoted context omitted.

Global access to the entire problem is definitely what we want to avoid. Encapsulation is a useful tool for maintaning invariants, controlling access to data and so forth. We don't want to give it up, and we may not even be able to give it up to begin with. One way to consider an environment is that it may be a physical machine with a set of bindings, variables and processes on it, where some of the bindings point to…

Ah, you inspire the thought that we don't necessarily know what the valid inputs are to a distributed program! But realize that this problem will bite you even higher up the food chain (so to speak) because what if there is more than one dog node? What does your program do in that case?

An environment can only bind a symbol to one expression, so the symbol "dog" in the tree environment is bound to an opaque reference to an environment (or machine), containing the dog's bindings. There can be many of these machines containing dog's bindings, but if you were to bind them all to dog, you would only have access to the most recently bound one, since binding a name in an environment overwrites any existing binding with a new one.

The solution is therefore, that we must either use bindings such as "dog1", "dog2" and whatnot, or we have a binding "dogs" which binds a list of environment objects, which we can index to refer to a specific dog. Actually, "dogs" could itself just be an environment which binds specific dog's names, like spike to their respective environments

  ($define! dogs ($bindings->environment (spike ($spawn ...)) (charlie ($spawn ...))))
This might be less useful than a list though as there's no way to map over the dogs in an environment.

Re: A late-night rant about OOP and parametric dispatch

#28

In a purely linguistic sense, the phrase "Object Oriented" does have some affinity to single dispatch, because the dispatch is oriented around a single special Object. "Message Oriented" flows well into multiple dispatch, as the entire message can be taken into account for determining the final method combination. This of course is related back to the notion of a language focusing on the nouns (Objects), or verbs (Me…

> "Message Oriented" flows well into multiple dispatch, as the entire message can be taken into account for determining the final method combination. I'm not sure about this. The metaphor is that you send a message to a specific recipient, and the recipient figures out, independently, how to behave in response to that message. So it still seems like single dispatch to me.

In Lisp/CLOS-style multiple dispatch, the method you call is not associated with any particular object. It's simply a dispatcher, and the objects are the parameters/message contents.

So you don't send a message to a recipient object, but dispatch it using a toplevel method name. I know that's a deviation from Kay's descriptions, but it meshes well with the examples of multiple dispatch that I've seen.

Conceptually, "dispatch" can be understood as finding the recipient, not an after-receipt task.

Re: A late-night rant about OOP and parametric dispatch

#29

Earlier quoted context omitted.

> "Message Oriented" flows well into multiple dispatch, as the entire message can be taken into account for determining the final method combination. I'm not sure about this. The metaphor is that you send a message to a specific recipient, and the recipient figures out, independently, how to behave in response to that message. So it still seems like single dispatch to me.

In Lisp/CLOS-style multiple dispatch, the method you call is not associated with any particular object. It's simply a dispatcher, and the objects are the parameters/message contents. So you don't send a message to a recipient object, but dispatch it using a toplevel method name. I know that's a deviation from Kay's descriptions, but it meshes well with the examples of multiple dispatch that I've seen. Conceptually, "…

Yes, I know how CLOS works. It's certainly a powerful approach, but it doesn't strike me as terribly... umm... object-oriented.

(0) In CLOS, as you say, methods are requests to the global environment to find the right behavior to perform on a list of arguments. In an object-oriented system, each object has its own behavior, and the rest of the program (that is, the environment) has no business knowing how it internally works.

(1) In CLOS, anyone can tamper with any object's slots, and this isn't even discouraged. In an object-oriented system, the internal representation of an object can't be directly manipulated from the rest of the program. The behavior of an object is defined entirely in terms of how it responds to messages.

That being said, you can do object-oriented programming with CLOS. It's just not the only, or even the main style that it supports.

For more information, see: http://wcook.blogspot.com/2012/07/proposal-for-simplified-mo...

Re: A late-night rant about OOP and parametric dispatch

#30

Earlier quoted context omitted.

In Lisp/CLOS-style multiple dispatch, the method you call is not associated with any particular object. It's simply a dispatcher, and the objects are the parameters/message contents. So you don't send a message to a recipient object, but dispatch it using a toplevel method name. I know that's a deviation from Kay's descriptions, but it meshes well with the examples of multiple dispatch that I've seen. Conceptually, "…

Yes, I know how CLOS works. It's certainly a powerful approach, but it doesn't strike me as terribly... umm... object-oriented. (0) In CLOS, as you say, methods are requests to the global environment to find the right behavior to perform on a list of arguments. In an object-oriented system, each object has its own behavior, and the rest of the program (that is, the environment) has no business knowing how it internal…

> In an object-oriented system, each object has its own behavior, and the rest of the program (that is, the environment) has no business knowing how it internally works.

I would say that describes Erlang-style message passing, or maybe JavaScript-style prototype OO. But in most "common OO" languages, I would say it's more correct that each class has its own behavior, not object. Of course, the hidden state of an object can parameterize the class's behavior.

But still, there are many behaviors which differ based on the type or identity of their arguments, but aren't well suited to be encapsulated into said objects as they're equal peer parameters to the message, hence CLOS etc.

Post reply on HN