“A closure is a poor man’s object; an object is a poor man’s closure” (2003)
people.csail.mit.edu
people.csail.mit.edu
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.
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.
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.
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
As Alan Kay puts it (paraphrasing): "setters turn an object back into a data structure"
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.
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.
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?
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.)
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".
That's actually a really good, concise definition. I might use this in future bikesheddings.
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.
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.
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.Only public fields provide unmediated access.
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.
FYI ruby has named arguments since a few versions back.
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.
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?
> 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.
(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.)
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.
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".
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 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.
Identity is merely a property of some objects, not all objects.
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.
that encapsulation must be all encompassing, and that identity is not meaningful or useful for transparent immutable data...
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.
"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
I didn't make any value judgements about the usefulness of immutable data structures.
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.
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.
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.
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.
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.
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.
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.
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.
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.
We are talking past each other because we don't possess compatible meanings of the word object.
Where we differ is which definition ought to deserve the label "object", in the pure sense the way we have a definition for "function".
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.
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.
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.
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).
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.
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.
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]".
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.
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.
(See, for example: https://www.allaboutcircuits.com/textbook/direct-current/chp...)
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.
Only the most simplistic application of closures (one unique set of instantiated variable slots per closure) matches object encapsulation.
The sharing and overlapping of data slots is however an inherent feature of closures.
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"?
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.
they can't? news to me. how does namespacing work, in say Python, then?
So I lean more towards closure being a poor mans object than vice versa.
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.
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.
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.
In that respect, I think the koan is perfect. :)
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.
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.
This is somewhere between "not even wrong" and "false".
Curious if this opinion extends to something like an Erlang process or a BSD thread.
Thanks btw for all your FreeBSD work :))
(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?')
The guy that invented OOP is on record as saying that.
[1] ftp://bitsavers.informatik.uni-stuttgart.de/pdf/borland/turbo_pascal/Turbo_Pascal_Version_5.5_Object-Oriented_Programming_Guide_1989.pdf
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 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...
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.
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.
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?
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.
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.
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?
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) !
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.
Structs and OO by contrast could be considered as premature composition. ;-)
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.
[1] Look for the instructions prefixed with FC = Floating-point Complex
https://developer.arm.com/docs/dui0801/latest/a64-simd-vecto...
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.
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's very convenient when you want your complex numbers to work just like ints and floats.
> 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!
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.
I mean objects are structures with pointers in the same way that OS is just an RPC server and databases are lists of arrays. ;-)
This fact doesn't offer us any insight into the nature of language constructs, though.
Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy.
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.
I agree though, when I dabble in C I often end up writing objecty code. Sometimes it's even a bit functional..
OO languages make this a lot more easy.
A JIT-ing interpreter, for example, doesn't have to use function pointers to implement objects.
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.)
No layer is truer than another IMO, it's hard to say which matter the most.. the abstraction or the implementation.
The cruel tutelage of Qc Na.
"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...
Languages which support nested functions usually allow those inner functions to be closures.
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...
[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
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.
"A closure with an empty environment" vs "a lambda" is a fine, barely-existent line. Or arguably identical.
> 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?
Maybe I'll write a blog post.
[1] http://umich.edu/~eecs381/handouts/Pointers_to_memberfuncs.p...
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.
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.)
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.
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.
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).
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.
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?
What does 'a capability' mean? People seem to use the term to mean everything from objects to threads.
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...
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.
https://en.wikipedia.org/wiki/Hacker_koan https://en.wikipedia.org/wiki/Danny_Hillis https://en.wikipedia.org/wiki/G%C3%B6del,_Escher,_Bach
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.
The classic example is a closure with message-passing, which was the original concept in Smalltalk.
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.
[1] http://www.ccs.neu.edu/home/shivers/ [2] http://matt.might.net/ [3] https://www.lexspoon.org/
(snark begets snark)
Would you please explain what about this amuses Scala programmers?