Hacker News new | past | comments | ask | show | jobs | submit login
“A closure is a poor man’s object; an object is a poor man’s closure” (2003) (csail.mit.edu)
289 points by noblethrasher on May 1, 2017 | hide | past | favorite | 227 comments



My definition of an object is "an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it."

Which makes for a very simple test: if something is an object, you should be able to send it a message that makes it change its mind about what messages it accepts after that.

There's no obvious way to implement a behavior like this using the native "objects" in nominally-object-oriented languages like C++ or Java. (But you can do so with the closure types from these languages. Or you can look higher on the stack: threads with IPC queues, or POSIX processes, are both objects.)

But implementing this behavior is obvious in Ruby; this is how basic things like Object#extend work.

And implementing this behavior is also obvious, oddly enough, in Erlang: http://joearms.github.io/2013/11/21/My-favorite-erlang-progr...

In conclusion, Erlang is more object-oriented than Java. ;)

---

And yeah, I know, this definition of OOP-ness sort of flies in the face of decades of things people have said about OOP. Objects, under this definition of OOP, don't obey any of the SOLID principles. They're slippery; unpredictable; "alive."

But this was the original definition! It's the one Smalltalk fits; it's the one LambdaMOO fits; it's what is meant by referring to Javascript as object-oriented.

It's a shame we have this collision between meanings; having two separate words for "OOP like Smalltalk" and "OOP like Java" would have saved language-designers all a world of arguments from people who try to propose extensions to a language in one category, assuming it's in the other.


Your definition of an object is similar to one that I’ve used[1][2][3], which is unsurprising since we got it from the same place.

But, now I define an object as any component with which you interact strictly by way of negotiation (as opposed to command and control), and I finally think I understand why Alan Kay says that the most important thing in OOP is the message passing.

Thus, statically-typed languages like C#/Java/C++/etc. are also capable of being object-oriented as long as you avoid setters and realize that some of the negotiation happens at compile-time.

[1] https://news.ycombinator.com/item?id=8679224

[2] https://news.ycombinator.com/item?id=11967631

[3] https://news.ycombinator.com/item?id=11809477


I'm not sure what's the problem with setters? In my eyes, they are just a convenient shorthand for a common message. For example, Objective-C before version 2.0 didn't have setters; so the libraries had lots of methods like "setTitle:", "setDelegate:" etc. Setters are just a convenient shorthand. And in ObjC and in Swift (I don't know Java/C#/C++ so well) you can override setters to do whatever you like, so there really isn't much of a difference between using a setter and sending a message -- it's just syntactic sugar that makes code more readable.


ironically the "java/c#/c++"-ness in this case is precisely a point of difference with objective-c, which has messages, while java has only method invokations. meaning a setter in c++ is (more or less) syntactic sugar for setFoo(objectref, newval), while in objective-c it's more like sendMessage("setFoo",objectref, newval).

the difference might seem minor but the extra layer of inderection means the target object can have centralised logic to handle the outcome of this message if it wamts to, as opposed to repeating and distributing such logic among every method, making it a more independent unit as opposed to something being directly manipulated from the outside.

put another way, the object can decide independently what to do with the message "setFoo" in objective C (or ruby), but in c++ there is no message, no decision, just some code that directly causes something to happen, that can be invoked directly from any other part of the program ar any time, whose results are then relied upon to be visible directly after invokation.

as a thought experiment, imagine if the target object actually exists on a different server, what would the different unsugared implementations need to look like? how would you pause the in progress program to wait for the result of the method invokation? what impact might that delay have on the user?


An object that derives from DynamicObject in C# can have conditionals in its TryInvoke override, so it can change which method calls it responds to. However this sacrifices useful static compile-time checks, so it only gets used in unusual situations where the extra dynamic behaviour outweighs the cost.

> whose results are then relied upon to be visible directly after invokation

I'm not sure I buy that, surely those details are down to the individual methods in question. There's lots of hairy (distributed) COM code out there that doesn't make that promise for example, and it won't hold in a multi-threaded environment for objects that are not explicitly thread-safe in any case.


By “setter”, I mean a mechanism by which code that is external to an object can gain unmediated access to one or more of the object’s mutable instance variables.

The big idea is that the sender should not be able to assume that the message, `setFoo`, will have any particular effect with respect to the reciever’s state.


It sounds like you are referring to public properties.


In languages like Lua you can always mediate what look syntactically like setters.


At least in C# terminology, a "setter" for a public property is mediated access, unmediated access would be a public field that can be set without invoking a setter method.

Getters and setters in C# are method calls, like `setFoo` in Java, so can't be assumed to be simple, they can do anything any other method can do.


As it turns out, C# is a language that I know extremely well; I'm aware that setters can have arbitrary code. But, here's the problem: A programming a language is medium for thought, which means that it promotes or prevents the occurrence of certain ideas.

So, even though the compiler will accept any legal void-returning code within the inner braces of

    public Foo Bar
    {
        set
        {
          //Do stuff
        }
    }
The fact that the client code is going to look like `Obj.Foo = whatever` will practically foreclose on all but a small subset of possibilities.


But classical setters aren't setters then, because the access is mediated (the are, or are backed by, methods which could do anything.)

Only public fields provide unmediated access.


Please see my reply to a sibling comment: https://news.ycombinator.com/item?id=14255796


I think the problem is that, yeah, they're messages but very low level messages about the state of the object and not at all about a behaviour of the object in response to a message.

I think, however, they're a good way of building objects where a language lacks object or hashmap shorthand (e.g JS and Ruby). In fact, with Obj-C, if my memory serves right, it can look just as expressive.

Setters (particularly fluent), in that case, seem best for building objects in a readable way where a language lacks a way to pass express keyword arguments.


I think in those examples, there are probably smaller objects hiding inside your bigger objects, and you should construct them first.

FYI ruby has named arguments since a few versions back.


I think you're probably right. That's the ideal.

Thanks for letting me know - can't believe I missed that. I don't use Ruby professionally, just as a scripting language to make my life at work easier.


That's true, and often a well-designed object needs setters, but I think what the parent is getting at is using objects to model dumb data instead of behavior encapsulating state. If an object has getters and setters for all its members, then it isn't really an object. It's a degenerate object that is equivalent to naked data. Not much changes if it has only getters: then it's just immutable naked data.

(Which I think is a good thing, and I hate it when people make blanket assumptions otherwise! And I hate it when languages like Java make it awkward to express dumb immutable data. I think the pieces in a program that are best modeled as "behavior" and "state" is a subset, usually a proper one, often a small one, of the data and functionality that needs to be modeled in a nontrivial program. Often there's a lot of data in a program that is most clearly modeled as dumb data, or at least as transparent data packaged together with some common operations. "Behavior" and "state" is mental overhead that only pays for itself when you have to express a certain amount of complexity. That's why it's so important for a language to let you start in one style, with plain transparent data (and lots of immutability), and switch to encapsulated behavior when you discover that part of the code is complex enough to benefit from it. Otherwise you pay a high mental cost to future-proofing every piece of data with getters and setters and private data when it's possible that only a small portion of your code will ever benefit from it.)


> If an object has getters and setters for all its members, then it isn't really an object.

Minor quibble. Take for example a coordinate class, that you can get/set cartesian, and get/set polar. It's a nice example of an object with no private state, but transparently hides the coordinate system transformations.

In general, i agree with you. The vast majority are just (possibly immutable) structs.


But, now I define an object as any component with which you interact strictly by way of negotiation (as opposed to command and control), and I finally think I understand why Alan Kay says that the most important thing in OOP is the message passing.

That's actually a really good, concise definition. I might use this in future bikesheddings.


Avoid setters?


Not parent, but I think setters destroy what's objecty about objects - that you send them high level messages and don't care what's inside of them.

As Alan Kay puts it (paraphrasing): "setters turn an object back into a data structure"


What's the difference between a setter in languages like C# and any other method call?

I agree that if you class Foo with a property:

public string Bar {get; set; }

and used as:

foo.Bar = "baz"

It does look like just another data structure. But what's the difference between that and having a method:

public void setBar(string value) { _bar = value; }

It is just as easy to add logic to the property later on:

public string Bar{ get{ return _bar; }; set { ...some logic; _bar = value; }

As it is:

public void setBar(string value) { ..some logic; _bar = value; }

In fact, they both compile to roughly the same thing.


For me a setter is something that refers explicitly to data inside the object. It means the world outside the object is now intimately and inappropriately concerned with what's inside the object. The whole point of objects is to make them block boxes and ask them to do higher level things, and hide how they do those things to avoid information overload.

Even if a method does nothing but set an internal field of an object, in my eyes it's not a setter if it performs an actual high level task and doesn't expose the internal structure. IE:

    monster.setDead(true);
Is a setter. While

    monster.Kill();
Is not to me, even if it does the exact same thing inside. The latter doesn't bother me about internal details like some boolean field called "dead".

It's a subtle and almost pedantic distinction, but I hope you see where I'm coming from, and can appreciate the mindshift change from "this is a box full of fields" to "this is an entity".


Thank you :) this is the answer I've been waiting for


I'd call that "still a setter". I think the concern is that if bar is more about the representation of the object than the behavior, you're not operating at the right level of abstraction.


And the other alternative, because an object still usually needs some type of values would be:

class Foo { Foo(string bar) { _bar = bar; } //Foo is the constructor }

The benefit being that once an object is created, it is guaranteed to be in a valid state.

But by classical OO an object represents state + behavior. So why not do both?

Assign the value in the constructor to guarantee that the object is always in a valid state and use a property (if the object is an entity as opposed to a value object), when it needs to change?


Why is it changing? have the method name reflect that domain reason, not reflecting the mechanical details of which internal fields need to change.


The thing is, in simple cases the implementation will trivially parallel the sensible interface. So setters are appropriate.

The problem becomes using setters where that's not the case, often to support tooling that is dependent on constructing objects from serialized state (which often also involves the same kind of abuse of getters, to which exactly the same concerns apply.)


To be clear, I wasn't endorsing the concern, just attempting to clarify it. It doesn't surprise me that it can be over-applied.


This is one of the few things about Ruby idioms that drive me a bit nuts--improper use of `attr_accessor` when `attr_reader` creates better and cleaner code is one of the leading code smells in the library ecosystem.


Is this to say that `attr_accessor` is too "heavy handed" when just using `attr_reader` could have sufficed?


It's not about heavy-handedness, it's that `attr_accessor` breaks encapsulation of private state and code very rarely takes into account what actually updating that member variable does.

It's used mostly because novice Rubyists are told that that's how you make a variable accessible, not that that's how you make it read-writable. In C#, on the other hand, when you create a property that's the moral equivalent, there's a lot more of a focus on creating getter-only properties that reveal state but don't let you munge it.

Ruby is also pretty bad about using #freeze and #taint, but the latter is a lost cause.


Obligatory Casey Muratory rant: https://www.youtube.com/watch?v=_xLgr6Ng4qQ


> My definition of an object is "an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it." Which makes for a very simple test: if something is an object, you should be able to send it a message that makes it change its mind about what messages it accepts after that.

I'm not sure these are equivalent criterion.

In Java and C++, the "dispatch matrix" is defined at compile time, and it is stored at runtime, and the object itself does determine the contents of that matrix. So these languages do clearly meet your first criterion. But also, clearly, not the second.

I guess the objection you might raise is "any/all messages", with "modify your dispatch matrix" being a reasonable message. But there will always be some message that cannot be implemented by the object; we can certainly code up some Goedelian thing inside of a core OO calculus. But for any concrete OO language, something more reasonable than that probable exists.

So then the question is where -- not if -- we draw the line on "any/all messages". And I'm not sure that drawing that line south of Ruby but north of Java makes a lot of sense.


Your definition makes a lot of sense to me. In pure programming languages, an object can appear when you run the code, but never before that. The pure description of the program cannot contain objects -- since objects carry state -- but when this description/code is executed/evaluated by the runtime system, a stateful object appears that you can interact with (e.g. a web server).

So, for pure programming languages, this "private state" is contained within the runtime system (out of reach for the programmer), rather than in the code itself, and the RTS applies pure functions to the state it carries, in order to reach a new program state.


To be entirely accurate, you're wrong when you say your definition of OOP is the original one. SIMULA-67 predates Smalltalk and its object system is much closer to Java than it is to Smalltalk, although it helped inspire both.

To be more fair, there are two very different schools which interpreted and continued to developed Simula in two radically divergent ways.

One of the schools is focusing on clear division between rigid, statically defined classes that never change (but may extend other classes) and runtime instances, which can contain only state, not behavior. This school does everything exhaustively codify the interfaces exposed by the classes: ensure that a predefined set of messages with a predefined set of parameters for each message (a.k.a methods) is permitted, control access to these methods and in the most extreme languages (e.g. Eiffel) even verify that object state and parameters conform to a predefined contract. This school is obviously not opposed to modifying behavior patterns based on runtime state, but it believes that the object system should not directly support that, and encourages programmers to implement their own mechanism on top of rigid-class object systems: this is what design patterns are usually meant to achieve.

The other school believes that an object system's first priority is giving objects absolute autonomy in parsing the messages they receive and as a result usually ends up with object systems that focus on powerful runtime extension and arbitrary message parsing functionality instead of compile-time strictness.

We could trace them back to their origins and call them the C++ school and the Smalltalk school. We could pin them to their modern champions and call them the Java school and the Ruby school. We could follow their poster boys and call them the class school and the message school. We could go on with type of languages they tend to thrive in and call them the dynamic school and the static school.

I would call either school "more object-oriented" than the other - they just have entirely orthogonal definition of what objects are. I can't even call either of them "better". I much prefer the Smalltalk school to the rigid version of the Java/C++ school (as was practiced in these two languages through most of the 90s and 2000s), but modern languages in this school have started incorporating some of the ML/Haskell tradition to give you much more powerful (and safer!) abstractions to define the way your objects may change their behavior in runtime. Design patterns are no longer encouraged, and if I'm using a language like Scala, Kotlin, Rust, Swift or even C#, I rarely find it necessary to employ a design pattern anymore.


A definition of objects that rules out immutable objects doesn't seem viable to me.

Furthermore, your definition merely requires mutability, which is a feature of Java and C++ objects, so I'm not sure what you think is lacking. Your definition reduces to "objects undergo state transitions".


Objects require at least an identity. Immutable data structures necessarily don't have identity (well, to say identity isn't meaningful because aliases can simply be seen as copies). There is more to it than identity, and you can always use a mutable variable or rebound parameter to recreate an identity for an immutable object (I.e. the world as an input and output).


I disagree. Encapsulation is the only universal property of object oriented abstraction (see Will Cook's papers on OO vs ADTs).

Identity is merely a property of some objects, not all objects.


I disagree with that and that particular paper. We must think of objects from a design perspective, not a technical one, and the one thing that distinguishes objects is their ability to be "things" rather than just constructs.

42 is not an object because the value can reproduced easily. John Smith, on the other hand, is not just a value, but has a name and an identity, is not exactly the same person everyday, and so on. 42 can be used in equational reasoning (it never lies) while John Smith cannot. John Smith, on the other hand, can be attributed with semi-autonomous behavior while such notion isn't meaningful for 42.


> We must think of objects from a design perspective, not a technical one

Why? Why should objects exclusively get this distinction when every other programming language paradigm is analyzed technically?

> 42 is not an object because the value can reproduced easily.

So?

What's the point of making "object" synonymous with "identity" when we already have the term "identity" to designate the relevant property?

Survey a bunch of programmers and ask if they agree with the phrase "objects respond to messages, and the only way to interact with an object is to send it messages". I'd wager a majority would agree with this statement. Which means procedural abstraction/encapsulation, wherein no details about object internals can leak to clients, is the only meaningful property of objects. 42 can then take on many representations, as long as they all respond to the same messages.


> Why? Why should objects exclusively get this distinction when every other programming language paradigm is analyzed technically?

Who is analyzing? I definitely don't analyze other paradigms technically. You should know me better than that. I'd rather all paradigms be analyzed based on how they are used to write programs and how they influence program designs. The technical details are just details.

> What's the point of making "object" synonymous with "identity" when we already have the term "identity" to designate the relevant property?

Again, it is how you design your programs: are you relying heavily on equational reasoning, or have you divided things into a bunch of nouns with behavior.

> "objects respond to messages, and the only way to interact with an object is to send it messages".

They might indeed, but it is a vacuous statement to begin with. It is extremely sad how little design is considered in programming languages. That an entire paradigm could be boiled down to such a true but useless statement.


> I definitely don't analyze other paradigms technically. You should know me better than that. I'd rather all paradigms be analyzed based on how they are used to write programs and how they influence program designs. The technical details are just details.

The technical details define how programs are structured. I don't see how you can separate these questions if that's what you mean by "design".

> Again, it is how you design your programs: are you relying heavily on equational reasoning, or have you divided things into a bunch of nouns with behavior.

Sure, but this doesn't preclude immutable objects from being used in subprograms that don't depend on state transitions. For instance, object factories may or may not be immutable, but if they are, they're suddenly not objects anymore? That distinction seems pointless.

> They might indeed, but it is a vacuous statement to begin with.

I agree that many people might take virtually anything to be a message, but when you try to specify with technical precision what that phrase actually entails, you get meaningful constraints, and I think Cook did a pretty good job distilling those properties.

For instance, "the only way to interact with an object is to send messages" means that no information can be revealed than what the object volunteers, ie. encapsulation, and this voluntary disclosure can't be captured statically to enforce stronger properties as with ADTs.


Design is like what does my program look like when I use X vs. what does it look like when I use Y? What are the implications of those two choices? I see objects and functions are pure design abstractions: you can easily emulate one or the other in a language that doesn't provide them as first class constructs, and even when they are provided as first class constructs, you can always use them in ways that are poor for the designs they support.

That "objects communicate via messages" leads to absurdities like numbers being considered as objects. It is completely blind to their benefits and drawbacks as anthropomorphic entities.


> That "objects communicate via messages" leads to absurdities like numbers being considered as objects.

It's only absurd if you're implicitly ascribing properties to objects that haven't been explicitly justified.

It seems perfectly reasonable to me that numbers would be objects, because numbers have a multitude of representations, all of which we'd like to be interchangeable. For instance, machine registers vs. arbitrary precision numbers. With ADTs, we'd have to explicitly define overloads for addition that handle the various permutations of parameters for addition of various number types, but a Cook-like object language wouldn't need this at all. Any representation of a number would be interoperable with any other implementation, automatically. That's neither vacuous or absurd, and these properties follow directly from taking that phrase describing objects to its logical conclusion.


When we come down to this, it is clear we just have very different of what objects are or are supposed to be. Interoperability (polymorphism) is at the center of the definition that you are using, and it is nowhere near as important in my definition of object, where polymorphism is possible with values and not related to their objecthood (it is a technical detail in how values can be made polymorphic).

We are talking past each other because we don't possess compatible meanings of the word object.


I don't think we're talking past each other, as I think we both understand the differences in each of our definitions, ie. I understand you think identity is central but I disagree, and you understand that I think procedural abstraction is central, but you disagree.

Where we differ is which definition ought to deserve the label "object", in the pure sense the way we have a definition for "function".


Even function has a definition in the philosophical sense unrelated to even the technical details of the lambda calculus. Especially when the function is pure and/or continuous, these are really useful distinctions in a design vs. using a bare bones definition that talks about a function being a pointer to some code.


> Even function has a definition in the philosophical sense unrelated to even the technical details of the lambda calculus.

Agreed, "function" has many concrete definitions, but arguably, there is some essence of what it means to be a function that we see reflected in each instance, and so virtually all of them are interconvertible in some real sense.

For "object", I see this essence as procedural abstraction. Every discussion of objects features access to data mediated by arbitrary code, which is how they implement behaviour. Object factories and numerical abstractions (fixed, rings, arbitrary precision, etc.) simply don't fit into your narrative of objects, so defining objects as requiring identity is immediately refuted in my mind.

Certainly you could argue that your approach to objects is better for software construction, but I don't think that means other approaches are no longer object-oriented, in the way that we can say that C permits you to violate the essence of functions.


I see objects as being completely unrelated to procedural abstraction, especially in its purest anthropomorphic form where objects have their own independent actuators and sensors (e.g. As in Kodu without any procedures at all, but also heavily used in etoys, scratch and of course Simula). In that case, they are just little entities (with identities of course) doing their own thing and communicating only through their environment.


Sounds like actors, not objects.


Actors are objects, of course. Do you want to ask Hewitt? :)


I think this thread is long enough! But I agree actors are objects, I just don't think all objects are actors.


It sounds like you're using "object" and "not an object" in the same way I would use "entity" and "value". I think this is Java terminology that gets used in C# sometimes too, I'm mostly thinking about NHibernate.

A value like 42 is fixed throughout time, it is always 42, and any 42 is indistinguishable from any other 42.

An entity on the other hand has an identity over time, so John Smith is always John Smith, even if he gets married and changes his name to John Brown.

Obviously it is confusing having different groups using the same words with different meanings, but that's just the way language works.


Language is always overloaded in the object-oriented sense. In the functional programming world, definitions are defined much more precisely, but then words have much less flexibility in how they can be used.


I don't really agree as well,

Modula-3 (although i'm not terribly familiar with the language) has partial revelation (a rather nuanced form of encapsulation), and branding where structural transparency does not preclude nominal identity... I tend to view the whole thing as a false dichotomy.


I'm not sure what you're disagreeing with specifically. "Partial revelation" sounds like a feature of ADTs they mixed into objects, which all modern OO languages have done, so I'm not sure what you think is the relevance to objects specifically.


I was kind of disagreeing with 2 things:

that encapsulation must be all encompassing, and that identity is not meaningful or useful for transparent immutable data...


> that encapsulation must be all encompassing

If we're trying to characterize "objects" in its purest sense, it must have some all-encompassing property. Mixing in other properties for pragmatic reasons doesn't seem relevant here.

> that identity is not meaningful or useful for transparent immutable data

No one claimed it wasn't useful, simply that it's not necessary to have objects.


>No one claimed it wasn't useful, simply that it's not necessary to have objects.

"Immutable data structures necessarily don't have identity (well, to say identity isn't meaningful because aliases can simply be seen as copies)."

I read saying that it is necessariy for them not to have identity, and that giving them identity isn't useful or meaningful.

If it said "don't necessarily" instead of necessarily don't, I would agree


My point here was whether they had identities or not wasn't meaningful, since they are (well, should be) purely judged by their value.

I didn't make any value judgements about the usefulness of immutable data structures.


Exactly what I thought you were saying, and disagree with.

I consider it fine to say "Objects require identity", without identity a fully encapsulated object is nothing more than an anonymous black hole.

When you turn this around to "Only objects require identity", or "identity is not meaningful for data", a hash code is one example of data which could benefit from identity, since you do not want to mix hash codes made by different hash functions. The positive integers 0,1,2... differentiated between the set of nonnegative integers are another simple

to preclude identity from data, is to preclude the ability to differentiate these on anything but the value level. surely a simplification, but by no means necessary.


Hash functions on values don't depend on identity and collisions are accepted as part of hash's definition. A GUID (and MD 5 hashes) for all practical purposes are identities.

Identities really mess up equational reasoning which is why they are basically missing from pure functional languages like Haskell. A lot of clever solutions like FRP involve solving what are otherwise straightforward problems without resorting to the use of identity.


Identities do not mess anything up, the commonly employed implementations of identity however do...

again check out modula-3 and more recently the paper at http://drops.dagstuhl.de/opus/volltexte/2015/5231/

Apologies I meant the identity of the hash algorithm e.g. MD5, attached to the bits produced by running the hash algorithm on an object, while hash collisions are absolutely accepted, mixing hash values produced by different hash functions is generally frowned upon.

Identity at the structural level is very much either you care about the identity, or you do not (in which case "this one"...), maybe 99.9% of the time "this one" suffices, however the takeaway from this should be that identity of structurally transparent objects should be ignorable, rather than structurally transparent objects should not have identity should one care to acknowledge it.


I don't follow why objects need an identity, as you define it. How does not having an identity make something un-objectlike?


I would say identity is core to objectness. Without identity, something is purely structural, only its value is relevant because it will not transition to another state later. Objects must be things you can manipulate. They have a presence beyond their values.


I use objects that can't transition from one state to another practically every working day. It's a strange view point to me that because something is immutable, it's not really an object. But a view worth thinking about.

To me the important thing is hiding internal state and sending high level messages instead of something like "change internal field x to y". Whether that internal state you're hiding is immutable or not seems irrelevant to me.


If you have a variable X that is pointing at different immutable maps over time, then X is the identity of your object that is implemented with multiple immutable maps.


Because objects are supposed to hide their internal state and the actions you do with them are heavily dependent on that state. This means that an object must have an identity to be sure you're working with the right object.

If you don't have identity it means that you must know yourself what state you're working with. This defeats the definition of objects that says that their state is hidden.


Encapsulation is not identity. Immutable objects are still perfectly encapsulated, but they don't have identity, which is what the OP was asking about.


I do come across the view a lot that immutable objects aren't really OO. It's something I really don't understand - state that changes over time doesn't seem like a key ingredient to me at all.


Erlang is an amazing language for learning how to effectively use OOP principles. I feel like anyone who works primarily with class-based languages owes it to themselves to write a few server-like modules in Erlang even if they never touch the language again.


Perl is also another interesting language for learning the low-level details of OOP, since its default set of OOP features (as of Perl 5; this all changes in Perl 6) involves manually "blessing" some data structure into an object. Objects are thus just "blessed" data structures (and not just hash tables, either, though those are the most common), and methods are just subroutines that accept their object as their first argument.

Between Perl and Erlang, I think I've ended up learning way more about real object orientation than I did from my dabbling in C++ and Java.


I have been thinking for a while that oop in the message passing sense is basically coroutines. I guess that is encoded in Erlang? Probably should learn it at some point.


>My definition of an object is "an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it." Which makes for a very simple test: if something is an object, you should be able to send it a message that makes it change its mind about what messages it accepts after that.

Your criterion doesn't match your definition.

Specifically "you should be able to send it a message that makes it change its mind about what messages it accepts after that" doesn't logically derive from "an object is an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it".

The second not only needs:

a) an object is an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it

but also implies b:

b) you can interact with the object and alter its private decision making process (and not just partially, e.g. only alter the data part of its runtime state).


Meh, that's a bit like arguing over "I could care less" vs "I couldn't care less", except far more pedantic because this time it's about an abstract concept that doesn't mean anything at its face value.

Object Oriented Programming is whatever the majority of people agree to it being, so as to facilitate communication. "Original definitions" don't matter. Alan Kay has no more right to define "Object Oriented Programming" than anyone else, you or me included, anymore than long-dead Thomas Jefferson has a greater right than the rest us to define the course of our country.

If you go on a job interview and they ask you "do you know object oriented programming?" and you say "oh yeah, I know all about that," then you're both going to be very disappointed on your first day of work.


> Meh, that's a bit like arguing over "I could care less" vs "I couldn't care less"

Obviously an aside, but...

While the former quite possibly arose as a corruption of the latter, I like to interpret it as "I could care less [but only if I really tried]".


Your definition sounds almost exactly like how an actor can handle messages and state, so it's no wonder that it's easy to implement in Erlang.


Your definition of an object sound more like and agent http://robotics.stanford.edu/~shoham/www%20papers/AgentOrien... [PDF]


I enjoyed building object systems in scheme. The closure way is kind of like focusing on the code without worrying to much about the underlying data structure. The object way is worrying about the data structure and the code is secondary.


I think in effect you're saying that they're duals.

I think I would argue that objects bundle data and functions in a useful way (when done right). Closures are more of an expressive convenience for constructing functions on-the-fly. The overreach for objects is to consider them as the only way to organize data and functions.


Duals has the right flavor, but my opinion is just an opinion, i won't try to defend that with any kind of formalism duals would imply.

I agree with you wrt objects bundle data and functions, but for my code, that sometimes causes cracks, disagreements about how i mentally model the code vs how i have to write it out. For example the visitor pattern is this little syntactic boilerplate dance to get a specific kind of behavior collected in one file. The closure way, you can build a special dispatch, and put all the implementations there.

I don't think there's a right answer. I just go along with what i think people will understand. There's a lot of value in having a standard and well understood dispatch system we all understand, even if it gets sort of odd in some cases.

With the closures, dispatching can be as crazy as you want. Create cycles of A extends B, B extends C, and C extends A for example.


What is a dual?


It's a table in all Oracle databases that has nothing in it. Also, it's this: https://en.wikipedia.org/wiki/Duality_(mathematics)


People who have studied electrical engineering may recognize the word "dual" with the prototypical example in electrical networks: Norton vs. Thevenin. Both are equivalent but from coming from opposite ends. A current source in parallel with a resistor (Norton) is electrically equivalent to a voltage source in series with a resistor (Thevenin).

(See, for example: https://www.allaboutcircuits.com/textbook/direct-current/chp...)


I actually studied carefully the Koan from the Japanese Buddhist traditions a few years ago. I can't tell you much about Japanese Buddhism, but I can tell you a lot about the Koan.

Not all Koan share all the same themes with each other, but for my money, a contemporary Koan really should try to embody all of a few important themes that I find repeated throughout the Koan.

A lot of non-experts seem to want to emphasize how often someone is struck, but for me I think this comes from a one of the most interesting themes in the Koan: a sense of humor about failing to attain enlightenment.

When a person attains enlightenment in the Koan, it is often at the moment when they are forced to acknowledge the duality of two opposing views, as if in the fleeting moment between seeing Rubin's Vase or the faces in its negative spaces.

In that sense, at least, it is a perfect Koan.


Multiple closures can overlap in their referencing of some set of variables. Objects can't really do this, so they're not that similar.

Only the most simplistic application of closures (one unique set of instantiated variable slots per closure) matches object encapsulation.


Objects containing references can share referents.


That's a feature of the references, not of objects.

The sharing and overlapping of data slots is however an inherent feature of closures.


> That's a feature of the references, not of objects.

That doesn't seem like an unreasonable way to draw the lines.

> The sharing and overlapping of data slots is however an inherent feature of closures.

Is it? When defining a lambda in C++ you can choose for things to be captured by copy rather than reference. I agree with saying that a function that captures nothing is "not a closure", but I don't know that I'd say the same about something that captures only by copy. Would you say that a language that only supported capture-by-copy "lacks closures"?


> That doesn't seem like an unreasonable way to draw the lines.

It's a turing tarpit otherwise, after all.

Regarding C++, I've not used the newer versions, but using that feature to capture only copies would match what I said upstream, as a limited usage of closures that would happen to match objects, while closures themselves can do more. Capturing by copy also means that it's instantiating a new closure variable slot specific to the copy, and is no longer closing over the original variable, so it's an additional step on top of the fundamental closure mechanism which would by nature be agnostic to private vs shared variable slots.


> Objects can't really do this, so they're not that similar.

they can't? news to me. how does namespacing work, in say Python, then?


While I've seen various methods in scheme to emulate objects with closures, I haven't seen a good one from the statically typed FP camp (the ML family, Haskell, etc). Meanwhile, objects are able to masquerade as closures very nicely in statically typed OO object systems like Scala and C#.

So I lean more towards closure being a poor mans object than vice versa.


OCaml, Objective Caml, has a built-in Object system. Objects are immutable which prevents them from carrying identity (though mutation can be opted into to recover this). They're also "just" row-typed records. Classes are a form of constructor function which have inheritance for code sharing purposes. This isn't emulating objects with lambdas, but it is a well-articulated idea of what objects in a pure-ish FP setting might look like.

There have been many attempts to bring various forms of "object" into Haskell. It tends to be either a very simple or a very messy affair. On the messy side, strict purity blows up object identity again and there are only very messy ways to recover it. This makes most OO such a pain that you're more likely to factorize it into smaller pure components which appear more functional.

On the simple side, corecursive patterns are distinctly "object like" and are used all the time. Laziness gives you infinite data structures which, in effect, give you a limited form of mutability which can be incredibly helpful while still be easy to reason about.

---

Finally, I think that practically there is a strong case to be made for introducing actor-like objects into Haskell. It has a great runtime and exception system to support it, and it would allow a bit more structuring "at the highest level" of application architecture. Today these are often managed as imperative programs (built an IO chunk, execute it). This tends to work, but Erlang did it better.


OCaml, Objective Caml, has a built-in Object system. Objects are immutable which prevents them from carrying identity (though mutation can be opted into to recover this). They're also "just" row-typed records. Classes are a form of constructor function which have inheritance for code sharing purposes. This isn't emulating objects with lambdas, but it is a well-articulated idea of what objects in a pure-ish FP setting might look like.

Ocamls objects are great. I'm familiar with them and a big fan. I wish there was a whole language based around them, ie everything is an ocaml style object.

That having been said, they do feel odd and out of place in Ocaml, and people don't seem to use them much.


To extend your point, this was written in the context of Scheme, which like most Lisps, is not statically typed.

In Lisps, it's trivial to build up an object with closures. (Create a fn that accepts some data, then return a data structure with fns that close over that data, and voila, object.) Building an OOP system is Lisp is a homework exercise.


Perhaps unsurprisingly, Oleg rebuilt the linked implementation in Haskell [0]. It works exactly as you'd expect, right out of the box.

0. https://arxiv.org/abs/cs/0509027


The haskell code looks much hairier. Then again I don't know haskell very well, and he did have to spin this out into its own paper.


Both are valuable under the right circumstances. A language that supports both is stronger than a language that only supports one or the other.

In that respect, I think the koan is perfect. :)


What does "stronger" mean in this context?

According to this definition, assembly is stronger than Haskell, because it contains stateful objects (registers), whereas a state-containing object cannot be present in Haskell code, although one may appear when you run the code in question.


Stronger means higher leverage for the typical programmer. Assembly language isn't very high leverage (I say that having written several games professionally in entirely assembly language).

Assembly language also doesn't natively support either closures or objects, except in the sense that of course you can build them from first principles, Turing Completeness and all that.

Until someone proves to me that Haskell can be used in a reasonable way by a "typical" programmer, I simply won't think about pure-functional when I make general statements about languages.

I fully admit this may be a hole in my brain, but I don't understand how to break down the problems I face on a daily basis into Haskell (or any pure-functional language), so I just talk about the universe of languages that are not pure-functional.

I've looked at Haskell tutorials and gotten at least hip deep into the language, by the way. I just usually end up dealing with problems that require levels of complexity where FP tools fall down. Your mileage may vary.


> whereas a state-containing object cannot be present in Haskell code

This is somewhere between "not even wrong" and "false".


They're both wrong. Closures and Objects are both structures with function pointers and delusions of grandeur.


Which is funny because I could never get anyone, when I first started programming in C and everyone was raving about how great 'object oriented programming' was, to tell me what 'an object' actually was. When I started learning C++ I was somewhat disappointed to find out an object was just a struct with some functions attached.

(It's kind of like when people told me about "user defined data types" and I couldn't wrap my head around how you could create a new data type with all its associated syntax. Then when I learned that they were just bundles of existing data types, though 'oh is that all?')


> I was somewhat disappointed to find out an object was just a struct with some functions attached

> when I learned that they were just bundles of existing data types, though 'oh is that all?'

Uh-huh. When I learned how everything just translates to 0s and 1s in the end, I was also super "disappointed". Higher-level constructs and abstractions are new compositions of lower-level building blocks? How.. plain!


And all those zero and ones are still translated into electrons. Decomposing abstractions is often not very useful.


And who says electrons exist as physical objects. They're just a mathematical abstraction physicists use to predict the results of experiments.


Then you will see it is not the bit that flips, it is only yourself.


They should put that in the 101 textbooks, so we won't waste decades writing billions of LoC!


The irony being that the spoon was CGI and hence composed entirely of bit flips.


Think about the difference between an int and a string, or a string and a function pointer. Now think about the difference between an int and a pair of ints. See?


I love OO - but C++s objects are so bad that most of the time I just end up using functions-where-the-struct-is-the-first-argument, C style. If I'm feeling particularly energetic I might use the PIMPL idiom. But naiive use of C++ objects is absolutely painful for me.

All language constructs can be broken down into simpler ones, at the end of the day. We might talk about how subroutines are just overrated assembler macros. C++ isn't very clean so the fact that these fancy schmancy objects are just structs + function pointers is very obvious. But when done cleanly, I'd argue that an object is more powerful than the sum of its parts.


"Actually I made up the term "object-oriented", and I can tell you I did not have C++ in mind" - Alan Kay


Good thing then that Stroustrup didn't have Smaltalk in mind when he designed the C++ object model. He based it on Simula which predates Smaltalk.


> I just end up using functions-where-the-struct-is-the-first-argument, C style.

I've been saying this for a while: of all C++/Java-style OOP we're mostly saving uniform function call syntax (cf. go, rust, nim, D and a bit C++17) !


But when done cleanly, I'd argue that an object is more powerful than the sum of its parts.

I think I agree with your assessment of C++. What languages do you feel deal cleanly with objects so they exceed the sum of the parts?


Honestly pretty much all of them if C++ is your reference point. Java, C#, Scala, Ruby, Ocaml...


I've seen objects recreated in Scheme and Clojure. They are really much more common than most FP people appreciate, just look for noun-ish naming conventions in your abstractions.


It's controversial for some people. OO is that horrible verbose enterprise code with all the mutable state. And if you suggest that certain features or patterns approximate OO they get very defensive.

There are certain famous FP language implementers that should probably read what Guy Steele wrote in the topic, because listening to their talks I strongly get the impression they haven't.


One could also just use a synonym of object, like "entity." "Hey...we aren't using objects here, we are using entities!" (Facepalm)

I saw Guy's growing a language talk before, but I don't think he's ever chimed in on this debate before. Most people at his level simply except the world without much bias, and are comfortable using object and functional styles when necessary.


C++s objects are so bad that most of the time I just end up using functions-where-the-struct-is-the-first-argument, C style

Can you explain more in depth why exactly? Is it because of e.g. http://stackoverflow.com/questions/5989734/effective-c-item-...? Or just not liking the syntax? Or private members?


High-school-me bounced off several Java and C++ books with their stupid "sally wants to buy flowers and john's flower shop" explanations of objects were before finally realizing, while using an OO PHP library, what was actually going on. Felt like people'd been deliberately obtuse to keep me from learning. "It's just structs with functions attached and a couple reall-easy-to-explain sugary features on top" would have been much better.


> Felt like people'd been deliberately obtuse to keep me from learning.

When I get that feeling, what's really happening 99% of the time is that the person trying to explain actually has no idea what they're talking about, but doesn't want to admit it.

If you understand a concept well enough, you can explain it simply and succinctly (barring things like really esoteric maths/physics, but nothing in computing is that difficult in my experience).


I will be forever grateful that an appendix to one of the Turbo Pascal manuals described exactly how objects were laid out in memory (including function pointers).


Turbo Pascal's manuals came with a separate volume dedicated solely to OOP. It's one of the best technical manuals I've ever read. Beautifully typeset, too [1]. Borland's technical documentation was really, really good at that time.

[1] ftp://bitsavers.informatik.uni-stuttgart.de/pdf/borland/turbo_pascal/Turbo_Pascal_Version_5.5_Object-Oriented_Programming_Guide_1989.pdf


Looks like what I was remembering as being an appendix was actually the last chapter of that volume: "Inside Turbo Pascal"


> Felt like people'd been deliberately obtuse to keep me from learning.

Well, that's one way to put it. What's really going on is they are trying to describe an abstraction from a user's perspective. So you could say the point of abstraction is to be "deliberately obtuse" in a productive and healthy way.

It's also common for people to try and fail to abstract things. Bad abstraction can be a net negative. It's likely that was what happened here.

And, to agree with you, OOP as a taxonomy of physical objects is both a pervasive and a bad idea. The point of classes and objects is to group data and functionality together. The things you do with a "Rose" may or may not look like the things you do with a "Cactus". Forcing them into the same hierarchy of behaviors because they are both "Plants" is injecting extra requirements into the system.


Yeah, honestly, I think I didn't "get it" until I read about vtables and the use of ecx as the "this" pointer while learning reverse engineering.

I think I struggle to learn a lot of functional programming concepts now because it's even more distant and abstract explanations.


I think I struggle to learn a lot of functional programming concepts now because it's even more distant and abstract explanations.

I've come across this a lot. Some people (like you perhaps) view computing in terms of machines, and others in terms of mathematics and formalism. (I'm lazy and lack principle, so I will go with whatever interpretation is easiest.) FWIW, I've met at least one incredibly smart person from both camps.

If you're into compilers at all, you might appreciate this[1] - how the "machine tribe" and "lambda tribe" view the same thing.

[1] https://wingolog.org/archives/2011/07/12/static-single-assig...


I understand the feeling, but object inheritance goes a bit beyond this model, doesn't it?

You could have, through some convention, the inheritance tree encoded into structs, but that's true of every language feature really. At no point do mots language feature stop being "structs + functions", because that encodes data + code.


My experience with inference-based programming does deviate some from that model. Data is scattered throughout a knowledge base in individual data points & statements, and only the current ephemeral binding set is the organizational glue that gathers the data together for operation.

Structs and OO by contrast could be considered as premature composition. ;-)


Inheritance = nested structs


Well, it's nested structs whether you like it or not. There are pros (no messing with private state in new and untested ways) and cons (you get all the private state even if you only need the one boolean) associated.


What if you needed to use complex numbers, and your language only supports ints and floats? Is the library or class used to implement the complex data type just a bundle of existing types?


Well, yeah. Usually somewhere in there it'll have

    class Complex {
        double real;
        double imaginary;
    }
Or similar. Maybe template it so you can have a Complex<double> instead. It's still fundamentally a combination of existing types.


That doesn't change the usefulness of being able to create new data types from existing ones. With operator overloading or operators as methods, you can treat user your new data types as primitives.

That's very convenient when you want your complex numbers to work just like ints and floats.


I didn't mean to say it's not useful, just that it's much simpler than actually defining a new primitive type, which is how I originally interpreted the term.


Yes. There aren't to my knowledge any CPUs with special instructions for complex numbers, they're implemented as wrappers ontop of the standard stuff.


If we're talking about CPUs, then structures and functions are just bundles of existing bit patterns and machine instructions.

Reducing things to just their primitives ignores how useful abstractions are for humans. Not everyone thinks it's a good use of their time to reinvent classes & objects or closures with structures and function pointers. Thus why those abstractions are part of so many programming languages (to answer the expressed bafflement one level above my initial reply).

Some C or assembly fans act like abstractions should end at that level, for some strange reason.


I didn't say anything about it not being a useful abstraction, just that it is essentially just an abstraction.


ARM has special instructions for complex numbers[1]. Modern CPU architectures usually have vector instructions which should cover most common complex number operations as well.

[1] Look for the instructions prefixed with FC = Floating-point Complex

https://developer.arm.com/docs/dui0801/latest/a64-simd-vecto...


> There aren't to my knowledge any CPUs with special instructions for complex numbers

There are lots of processors out there besides CPUs. You could even design a new processor or program an FPGA to do complex math. Fast Fourier transforms deal with imaginary math, for example. You can do those in a number of ways, including on a CPU or GPU, but it's not unheard of for companies to use either custom hardware or programmable hardware to meet performance needs.


Intel CPUs have specific vector instructions specifically meant to accelerate operations on complex numbers (ADDSUBPS for example).


> Then when I learned that they were just bundles of existing data types, though 'oh is that all?'

You don't have to use existing data types when defining your own if you really don't want to. It's not 'just' that, it's about setting up your own behavior.


Well, to be fair, "objects" as originally envisioned were not like C++ implemented it.

The guy that invented OOP is on record as saying that.


And in practice usually with an implementation of lots of useful functionality and good integration into the type system.

I mean objects are structures with pointers in the same way that OS is just an RPC server and databases are lists of arrays. ;-)


And lists are just cons cells.


Simplifications are just ordered letters!


Con cell is just a 2-tuple node in a singly linked list. When do the definitions all end?


Can't agree with this at all. Bare function pointers are inadequate for many purposes; using them often requires explicitly passing around an additional environment pointer ('qsort_r' on Linux is an example), which is ugly and not type-safe.

What you see as "delusions of grandeur" I see as the elegance of two concepts which, while duals with each other, are much more general than bare function pointers.

(And yes, I understand very well how closures and objects are implemented using function pointers.)


You don't use objects because they are function pointers. You use object because it makes establishing contracts between them convenient. Closures can't replace that.


That's an implementation detail.


Exactly! But because this is how objects are implemented, everything you can do with objects, you can do using structures and function pointers -- there's nothing magical about objects. Which means that as long as you understand structures and function pointers you don't need objects.


Right, but all programming language constructs are eventually reduced to assembly code. So, strictly speaking, as long you understand assembly, you don't need any language constructs at all.

This fact doesn't offer us any insight into the nature of language constructs, though.


Exactly. To quote Alan Perlis:

Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy.


Embedded C programmer here. What you say is technically true, however, wiring interfaces and vtables manually in C is a real PITA, to the point that you think twice before introducing a new interface between two components.

OO languages make this a lot more easy.


That is not always how objects are implemented. It is but one way to implement objects.

A JIT-ing interpreter, for example, doesn't have to use function pointers to implement objects.


Of course a design centered around structs equipped with function pointers is, in fact, an object system.


Sure. But realizing that objects are just structs with function pointers takes you from a binary "this code uses objects" vs. "this code doesn't use objects" to a more flexible "this code is designed in a more objecty way than that code".

A lot of people think about objects as a language feature, when they're really a design paradigm; and like all good design paradigms, you can use more or less of them depending on how well the paradigm fits a particular piece of code.


To an extent - with the caveat that there are entire languages built around this design paradigm, such that using objects is the syntactic past of least resistance.

I agree though, when I dabble in C I often end up writing objecty code. Sometimes it's even a bit functional..


Colin,

Curious if this opinion extends to something like an Erlang process or a BSD thread.

Thanks btw for all your FreeBSD work :))


I don't know enough about the implementation of Erlang to answer that question.


I'm sorry to dissect your quote, which is hall-of-fame-worthy, guess I'm just curious if it's always turtles all the way down, or if there is a "right" level of leak-proof abstraction. I imagine that every abstraction leaks in certain contexts, but maybe certain ones have stuck just because they're "good-enough" for common cases. I suppose I brought up processes and threads since they might provide for a level of parallelism that raw objects or closures lack, without an OS or runtime supporting them.


I disagree, structure is an encoding scheme that embodies some interface ! :)

No layer is truer than another IMO, it's hard to say which matter the most.. the abstraction or the implementation.


By the same token, structures with function pointers are just Turing Machine bitstrings with delusions of grandeur.


Bah! Function pointers are glorified gotos.


No, function pointers are glorified addresses. Function calls are glorified "push the instruction pointer" plus goto.


technically correct, but you know what I meant :)


'Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.'

The cruel tutelage of Qc Na.


The Zen Slap:

"If you read through enough Zen koans, you’re quickly struck, as it were, by the number of those oblique lessons that end with a student getting slapped. It may be delivered by hand, stick, sandal or goat bite, but the slap, painful as it might be, is an act of compassion, a wordless reminder of the things we know but can never seem to remember or practice for very long."

-- http://www.siliconbeat.com/2008/10/09/of-course-if-theres-no...


The TV show NCIS borrows from this philosophy. Gibbs always smacks the back of his underling's heads when they do something stupid. But if he doesn't, they worry he has given up on them. They also made it a point saying that smacking the front of the head would be offensive.


Really understanding this means knowing why a closure is a poor man's poor man's closure.


When you get to my depth, you'll understand that a closure is a poor man's poor man's poor man's object. And it smells like a turtle, for some reason.


The main use of closures today is to create something you can pass to a callback. In theory, you could pass an object, but the callback would have to cooperate by accepting an object and an object method, or a reference to an object method/object pair, in languages which support that. This requires more standardization than one is likely to get.


> This requires more standardization than one is likely to get.

Not for the functionality - the only difference is that your compiler cannot tell you that you are doing it wrong while a strict interface can be checked at compile time. But the standardization is there, it's just not explicit - your programm will crash/fail when you don't provide a proper callback that obeys the implicit contract (e.g.: takes 2 arguments, first the value, second the error if any; returns nothing).


> the only difference is that your compiler cannot tell you that you are doing it wrong while a strict interface can be checked at compile time

In most compiled languages with closures, the parameters are type-checked. In some, the return type is also type checked, but there are typically ways to add type checking to the return type if that's important.


I used a closure for a callback less than 1% of the time. Closures are .. closed pieces of information to be combined (through apply). See untyped monadic style, or lc-encoding of lists, functional composition (f . g => a new function, not a value).

I closely related closures to partial application. But mostly.. closures are anonymous objects tiles, you can create one on the fly as needed without cruft.


I think of closures as a bit orthogonal to that; the definition of "closure" that I'm used to is a function that "encloses" some data that is not an argument (somewhat like a function with free variables in lambda calculus), whereas a callback might only deal with its arguments. That being said, sometimes I see the term "closure" being used in a way that I associate with the word "lambda", which feels a bit off to me (I'm looking at you, Rust...)


Lua creator Roberto Ierusalimschy just gave a talk about "Functions in Lua" and starts his talk explaining that our (conversational) language has still not evolved a precise way to articulate all these different ideas about functions.

On his first slides, he shows the words, "anonymous functions", "lambdas", "closures", "function values", and "first-class values", and shows how each has subtly different meanings/implications and tends to take us down different lines of thinking which affects the conversation you are trying to have with somebody else.

https://www.youtube.com/watch?v=wdRGOE1N-FA


A lambda is an anonymous function. A closure is a function which carries with it some state. The two concepts are independent - you can have named functions as closures, and lambdas that aren't closures. Rust does seem to tie the concepts together syntactically.

Languages which support nested functions usually allow those inner functions to be closures.


This is my understanding of what the terms mean as well, but in Rust, any anonymous function seems to be referred to as a "closure"[1], which is what feels strange to me.

[1] See the first example here (which only operates on its arguments but is still referred to as a closure): https://doc.rust-lang.org/book/closures.html


Yeah, any time you have a nested function you raise the question of "what can this close over?" Sometimes the answer is "nothing", and that's not a closure... but it's still an answer to the question so it makes some sense to talk about it in the same breath.


Lambdas are just functions with different syntax. Closures imply keeping some state around, and may require that some variables from outer scopes live longer than they otherwise would. (That's what they're usually used for in Javascript, where you want state kept around for a reply event.)

Closures are easy to implement in garbage-collected languages, but get complicated in non-garbage collected languages. When an inner function normally references variables in an outer scope, they're just references to the stack. But if there's a closure that can outlive the outer scope, those variables have to be saved somewhere.

With C++11 closures, you have to specify what you want to keep around and how you want it kept.[1] Internally, C++11 creates an object to store the variables you want to keep around. This can potentially create lifetime problems, because you can save a reference to a variable, then keep the closure object around longer than the variable being referenced. This creates a dangling pointer. Rust has lifetime checking to catch such errors, but C++11 does not. See this Stack Overflow discussion of how not to shoot yourself in the foot with C++11 closure lifetime undefined behavior.[2]

That's why it's useful to realize that closures and lambdas are not the same thing. Lambdas are simple. Closures are complicated.

[1] http://www.cprogramming.com/c++11/c++11-lambda-closures.html [2] http://stackoverflow.com/questions/27775233/lambdas-and-capt...


I certainly don't mean that there aren't contexts where distinguishing the two is vital to understanding. I just mean there are context where you'll need to talk about both, and I think that's part of the reason they sometimes get conflated.


Rust's closures are, well, closures. But many of them are effectively lambdas. It's not like we can force you to refer to your environment or something :p

"A closure with an empty environment" vs "a lambda" is a fine, barely-existent line. Or arguably identical.


Wait, what? A closure can be a lambda and a lambda can be a closure, but you can have closures that aren't lambdas and lambdas that aren't closures. Why link them together at all? "A closure with an empty environment" is only a lambda if it's unnamed, otherwise it's just a function.


I think the burden of proof is the opposite: why not link them together? What are the benefits of having closures and lambdas be two distinct language features when having just one seems to suffice (specifically, having a lambda be a degenerate closure)? As for naming, that's an orthogonal issue, as a closure can be anonymous just as easily, and I see no use in drawing a semantic distinction between anonymous "lambdas" and nonymous "functions".


What on earth are you talking about? The definition of "lambda" is "anonymous function." Naming is orthogonal to closures, yes, but it's the entire difference between a lambda and any other kind of function. A lambda can close over state or not, it doesn't matter. It just needs to not have a name. So if naming is orthogonal to closures, then lambdas are orthogonal to closures, and then it makes no sense to compare them.


> The definition of "lambda" is "anonymous function."

> A lambda can close over state or not, it doesn't matter.

I've never heard of any definition of "function" that allows closing over state, that's the demarcation that we use to distinguish a function from a closure. What taxonomy are you trying to propose? And going back to my original question, what is the practical intent of trying to make a lambda a semantically-distinct concept from a closure?



In the phrase "anonymous function", "function" means anything that you can call. This includes closures. I'm very surprised you've never heard of the word "function" being used that way.


I guess in my mind, "a closure with an empty environment" is a contradiction, but as mentioned elsewhere in the thread, there isn't a lot of consistency to the way we use these types of terms (closure, lambda, etc.) across different languages and paradigms.


Yup. I also think it matters if you come at it from an implementation angle or a math angle. It's a contradiction from the latter, but from the former, it may not be.

Maybe I'll write a blog post.


Callbacks only deal with their arguments, but almost every callback I see in the wild has a first argument "void * cookie" which immediately gets typecast into a pointer to the structure (oops, I mean object) being manipulated.


Right, that's how you do it in C, where you don't have closures. Closures take some heavy machinery behind the scenes in a hard-compiled language. They sort of fall out of the dynamic binding mechanism in Python and Javascript.


I'm confused by your terminology here. Doesn't "callback" usually just mean a first class function f you pass around - as a piece of data - to another function g, under the assumption that g calls f?

Ie

    function f(...) {...}
    g(f);

It looks like you're using "callback" to refer to "g" in my example, not "f". Is my terminology wrong, or is yours?

(This is a genuine terminology question, not an exercise in pedantry.)


Sorry, meant "callback argument", a function argument to which a function can be passed.

In Javascript and Python, all functions passed as parameters to other functions are potentially closures, but this is not the case in most compiled languages.


No worries. The callback terminology always confused me. I think it came from the C culture, where you need a special word for a tricky concept. Weird that it persists in javascript, where it's easy.


Or you can pass a reference to some object's method.


If that's supported in the language, yes. C++ supports this, but the syntax is special.[1] What's passed is not just a pointer to a function; it's really two pointers, one for the function and one with the "this" pointer.

[1] http://umich.edu/~eecs381/handouts/Pointers_to_memberfuncs.p...


note that, in C++, pointers to member functions do not close over 'this' [1], so they are not really a reference to a method of an object, i.e. you need to provide the actual object externally to invoke the pmf.

The closure need to be created explicitly with something like std::bind or an actual lambda, and passed around as an std::function (a type erased, generic closure wrapper) or as a template parameter.

/pedantic

[1] GCC does have a very old extension that actually creates the closure.


In Midori, objects are capabilities. This is possible because type safety is enforced and an object's type cannot be forged.

It's easy to imagine a capabilities system built around closures. For example, a capability to send on a socket could be an unforgeable closure that performs that sending. Are there any systems that work this way?


More details on Midori: http://joeduffyblog.com/2015/11/03/blogging-about-midori/ This is the first of several blog posts by one of the authors. I haven't read all of them, but liked what I read so far. Does anyone have other resources with more detail?


As a counterpoint, people would make poor-man objects in ancient Fortran by using array indices instead of addresses to reference the object. And you could use integers and lots of conditionals to represent polymorphism. You'd have to ban all sorts of things to lock out developers from making their own custom object systems.


> objects are capabilities

What does 'a capability' mean? People seem to use the term to mean everything from objects to threads.


In this context, it means the object capability model.

https://en.wikipedia.org/wiki/Object-capability

Which can be used as the foundation of security.

https://en.wikipedia.org/wiki/Capability-based_security

Relevant application in Midori:

http://joeduffyblog.com/2015/11/10/objects-as-secure-capabil...


Since objects are just specialized applications of closures, it's not too surprising that you can reconstruct the basic idea (closure) from its application (object).


I'm curious what's the history behind the use of Zen koans in computing? There seems to be a lot of that.

Maybe because in programming there, often, isn't a clear right or wrong (like in this case) so it pays to structure the problem/solution as a story to make people "feel" rather than reason.


> Maybe because in programming there, often, isn't a clear right or wrong (like in this case) so it pays to structure the problem/solution as a story to make people "feel" rather than reason.

This is pretty close to my thoughts. Though I would add that engineering often lets you solve a problem in several ways. In that way, there's no one "right answer". The exercise, instead, is to consider trade-offs. Koans (1) draw a bright line under the tension between competing but valid ideas.

(1) OK. Non-ironic koans. I consider the git koans to be mostly about poking fun at git's inconsistent porcelain.


If I had to guess, I'd say it all started with GEB by Douglas Hofstadter. It very well explains aspects of symbol manipulation using koans. However, there might be another precursor.


ESR attributes the invention of the "AI koan" to Danny Hillis "while a student at MIT". Hillis graduated in 1978. GEB was published in 1979.

https://en.wikipedia.org/wiki/Hacker_koan https://en.wikipedia.org/wiki/Danny_Hillis https://en.wikipedia.org/wiki/G%C3%B6del,_Escher,_Bach


Aren't they both just bundle of function objects + states?


Closure is more fundamental though. Add a structure (define an ADT) to a closure and you will have an object.

The classic example is a closure with message-passing, which was the original concept in Smalltalk.


Well, it seems that having a structure is the most fundamental principle. While in reality structures formed and maintained according to the laws of physics, in CS we have to explicitly define the rules, which is what Abstract Data Types (which define Interfaces) are. Protocols and Interfaces are mere generalizations of ADTs.

There are, however, at least some "laws of the universe" in CS. These are Interrupts (the only way to do I/O right), specialized Syscalls, etc. One should respect them.


One word: inversion of control.


(2003) My favorite thing about this thread is that Guy Steele is involved, and Guy Steele has a far better name than anybody else has ever had.


Guy Steele's friend Olin Shivers [1] was the PhD advisor of Matt Might [2] and Lex Spoon [3]. I'm pretty confident the entire field of programming languages research is a Marvel comic.

[1] http://www.ccs.neu.edu/home/shivers/ [2] http://matt.might.net/ [3] https://www.lexspoon.org/


I did work with a guy named Max Kalashnikov at Google. That was a pretty "rad" name, too.


I've also known a "Ulises Cervantes" and an "Aristotle Socrates," since we're trading awesome names :) Guy Steele is still my favorite, because I can just picture a mustachioed villain shouting "curse you Guy Steele!"


The visual here is awesome. Although all good programmers are antiheroes in my world, so I read that as a mustache twirling Guy Steele merrily evading the bumbling cops. Practically the whole point of Real Programming is to evade the limits of The Man. (Cf Mel: http://www.catb.org/jargon/html/story-of-mel.html )


Scala programmers are laughing from the side lines.


Which is where they'll remain.

(snark begets snark)


As Scala didn't yet exist when this message was written, its author was missing the enlightenment Scala provides.

Would you please explain what about this amuses Scala programmers?


Scala is largely a set of iterative improvements with a syntax that's designed to handle data workflows well. It's not a big deal, it's not that exciting, it doesn't use all of the latest technologies, and that's why I think it has a bright future.


This doesn't address closures vs objects at all.


F# also has object expressions that are just as syntactically convenient as closures.


From what little I know of F#, it seems a lot like OCaml (as in, I don't know enough to know of any differences, other than that F# is a .NET language). From what slightly more I know about OCaml, objects are there, but almost nobody uses them and everyone pretends that they're not in the language. Is F# similar in this regard, or are objects considered fair game?


Idiomatic F# code does tend to use pattern matching more than ad-hoc polymorphism, but objects are still very much considered fair game since the major value proposition of the language is its ability to leverage the .NET ecosystem.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: