Both objects and closures are modelled by a particular pattern involving existential types.
Here’s the definition of closures:
type Fn a b = exists e. {
env : e,
code : (e, a) -> b
}
apply : (Fn a b, a) -> b
apply(f, x) = f.code(f.env, x)
And here’s a “mutable counter” object that’s often used in OO examples:
type Counter = exists s. {
state : s,
next : s -> (),
read : s -> int
}
new : Counter
new = {
state = Ref.new(0),
next = Ref.modify (+ 1),
read = Ref.get
}
next : Counter -> ()
next c = c.next c.state
read : Counter -> int
read c = c.read c.state
The pattern has a generic name: the greatest fixed point of a functor (I will expand on this by request). Types that can be represented by the greatest fixed point of a functor are called conductive types, or codatatypes.
So I know what a (greatest) fixed point of a function is. I know an analytical way to calculate it and, as is often necessary, a numerical way to approximate it. What I’ve never managed to figure out is what the relationship between that kind of fixed point and the fixed point mentioned here is. What is the function here, how do you verify this is the greatest fixed point and why does it even matter that it is a fixed point in the first place?
The pattern has a generic name: the greatest fixed point of a functor (I will expand on this by request).
Would be great to read more on this; while the concept itself is intuitively clear to me, I’d very much like to hear what people can get out of a formalized version (pointers to reading material welcome).
I’m not sure about the generic form of the pattern, but Codata in Action is all about the connection between objects and codata and so might be interesting to look at. I’ve not had the chance to sit down and digest it personally though. Looking through the earlier sections seem reasonably accessible. There’s some more detailed technical parts later on, but if your not comfortable with that you can always jump off there and perhaps skip to the conclusion.
I can’t emphasize how much this changed how the way i program
I was writing the first version of my language (peacock) in functional style JavaScript & then at some point i realized all these closures were just messy ad hoc immutable classes.
Over a few iterations i landed in OO ruby & i never thought id say this years ago but OO is really really beautiful, over time i started to introduce more mutation when it called for it & i found the end result was significantly simpler & more maintainable code.
Moral of the story is dogma like “OO is bad”, “mutation is bad” is not only wrong, but such a limiting & IMO boring way to view the world.
With a closure you don’t list your data members, they’re just implicitly closed over. Sometimes that’s nice. Sometimes you want it to be explicit instead.
There’s a lot I could say that could easily result in a blog post of length. But to sum up where I stand -
This isn’t a comparison of JS object/prototype/class system vs ruby classes (though I think they are very different) since I wasn’t using the object system in my JS implementation.
I strongly believe so-called cosmetic differences are one of the most important factors on day to day programming quality & happiness.
Let’s make this a lot simpler (not for anyone who’s already posted in the thread because I can tell that y’all get it, but for anyone else who’s lurking):
Closures are functions that carry some of their own data with them.
Object instances are data that carry some of their own functions with them.
For example, imagine you had a speak-some-text() function that you wanted to carry a text and keep track of where in the text you are so you can speak the next part and then place a new bookmark. A closure can do that.
Or, if you had a book object that had members such as text and position and a method speak-at-current-position(). An object instance can do that.
So as you see you can do kind of the same thing.
What’s frustrated me in the past with object systems is when all I needed was a normal first-class function, I didn’t need to carry any state or data, but I had to make an object (with no members and a single method) anyway (because this was when Java didn’t have first class functions, maybe they still don’t, it was 15 years ago, IDC), and in order to make the object I had to make a class, it was a hole schlep for what’s trivial and like two characters in a language with first class functions like shell scripting, JS, or lisp.
What’s frustrated me in the past with SICP-style “closures are objects”, the kind of overwrought closing over a symbol-message dispatch (making kind of a Smalltalk or Obj-C style implementation), is that there’s no inheritance; you can work around that easily enough with composition or adapters but sometimes you just want a beautiful concise object system. ♥
Overall I prefer closures and use them all the time and I very rarely want an object system. When I do it’s because I’m trying to solve a Simula type problem like a GUI or a set of actors such as monsters in a video game.
What’s frustrated me in the past with SICP-style “closures are objects”, the kind of overwrought closing over a symbol-message dispatch (making kind of a Smalltalk or Obj-C style implementation), is that there’s no inheritance; you can work around that easily enough with composition or adapters but sometimes you just want a beautiful concise object system
There are so many object systems in Scheme to choose from that do provide inheritance one way or another. I always found the prototypical inheritance based prometheus very elegant (to the point that I misguidedly used it in the SSQL egg), but there’s of course also coops etc if you want something more fancy.
I’ve used coops a lot ♥ and of course tinyclos back in the day of C3.
A lot of my love for objects were out of the support for generic methods. These days since I have my match-generics I can get by with just records or call-tables most of the time.
That’s the beauty of Scheme, you can just create (or pick) the exact best thing for your use case. Whereas with your Java example you were basically stuck with what the language offered (unless you went the route of generating code)
Python is not a great language for demonstrating this though because you have to use nonlocal to assign to any closure variables. Going further, objects, closures, and just plain old passing a lot of arguments are all roads to the same end.
I gave a talk about this symmetry at BOB 2020 in Berlin. I’m linking the org-mode slides here in case people are interested. They discuss the symmetry in practice, and also some theoretical / more advanced elements.
Something that people often don’t recognize is that, while the Expression Problem suggests that OO programs are make it easier to add new data cases, while FP make it easier to add new operations (without modifying existing code), in practice in my experience readability is also affected: it’s easier to read code where all the logic is in the same place, so FP code makes operations easier to read and OO code makes “data cases” easier to read. I think this has potential consequence for code visualization tools.
Yes, if you have lots of types and few methods, OO wins; if you have many methods and very few types, imperative (or FP) wins. Where both break down are those cases where you have lots of types (or objects) and lots of methods.
Absolutely, as a Lisper myself, I had to swallow plenty of pride to use Python for this particular example. I quite like the code you posted in this comment.
Using closures to implement objects that respond to messages is also famously done in SICP. If you take this to its limit, you can implement a full class-based object system (even with a meta object protocol, if you like) using only closures.
Here’s the formalisation you’re looking for:
Both objects and closures are modelled by a particular pattern involving existential types.
Here’s the definition of closures:
And here’s a “mutable counter” object that’s often used in OO examples:
The pattern has a generic name: the greatest fixed point of a functor (I will expand on this by request). Types that can be represented by the greatest fixed point of a functor are called conductive types, or codatatypes.
Objects and closures are both codatatypes.
So I know what a (greatest) fixed point of a function is. I know an analytical way to calculate it and, as is often necessary, a numerical way to approximate it. What I’ve never managed to figure out is what the relationship between that kind of fixed point and the fixed point mentioned here is. What is the function here, how do you verify this is the greatest fixed point and why does it even matter that it is a fixed point in the first place?
Would be great to read more on this; while the concept itself is intuitively clear to me, I’d very much like to hear what people can get out of a formalized version (pointers to reading material welcome).
I’m not sure about the generic form of the pattern, but Codata in Action is all about the connection between objects and codata and so might be interesting to look at. I’ve not had the chance to sit down and digest it personally though. Looking through the earlier sections seem reasonably accessible. There’s some more detailed technical parts later on, but if your not comfortable with that you can always jump off there and perhaps skip to the conclusion.
Thanks so much for explaining this! I really appreciate it.
I can’t emphasize how much this changed how the way i program
I was writing the first version of my language (peacock) in functional style JavaScript & then at some point i realized all these closures were just messy ad hoc immutable classes.
Over a few iterations i landed in OO ruby & i never thought id say this years ago but OO is really really beautiful, over time i started to introduce more mutation when it called for it & i found the end result was significantly simpler & more maintainable code.
Moral of the story is dogma like “OO is bad”, “mutation is bad” is not only wrong, but such a limiting & IMO boring way to view the world.
What is messy or ad hoc about them?
I’ve coded a lot in both ruby and JS and my sense is that, like the article says, the differences are cosmetic.
With a closure you don’t list your data members, they’re just implicitly closed over. Sometimes that’s nice. Sometimes you want it to be explicit instead.
There’s a lot I could say that could easily result in a blog post of length. But to sum up where I stand -
This isn’t a comparison of JS object/prototype/class system vs ruby classes (though I think they are very different) since I wasn’t using the object system in my JS implementation.
I strongly believe so-called cosmetic differences are one of the most important factors on day to day programming quality & happiness.
Let’s make this a lot simpler (not for anyone who’s already posted in the thread because I can tell that y’all get it, but for anyone else who’s lurking):
Closures are functions that carry some of their own data with them. Object instances are data that carry some of their own functions with them.
For example, imagine you had a
speak-some-text()
function that you wanted to carry a text and keep track of where in the text you are so you can speak the next part and then place a new bookmark. A closure can do that.Or, if you had a book object that had members such as
text
andposition
and a methodspeak-at-current-position()
. An object instance can do that.So as you see you can do kind of the same thing.
What’s frustrated me in the past with object systems is when all I needed was a normal first-class function, I didn’t need to carry any state or data, but I had to make an object (with no members and a single method) anyway (because this was when Java didn’t have first class functions, maybe they still don’t, it was 15 years ago, IDC), and in order to make the object I had to make a class, it was a hole schlep for what’s trivial and like two characters in a language with first class functions like shell scripting, JS, or lisp.
What’s frustrated me in the past with SICP-style “closures are objects”, the kind of overwrought closing over a symbol-message dispatch (making kind of a Smalltalk or Obj-C style implementation), is that there’s no inheritance; you can work around that easily enough with composition or adapters but sometimes you just want a beautiful concise object system. ♥
Overall I prefer closures and use them all the time and I very rarely want an object system. When I do it’s because I’m trying to solve a Simula type problem like a GUI or a set of actors such as monsters in a video game.
There are so many object systems in Scheme to choose from that do provide inheritance one way or another. I always found the prototypical inheritance based prometheus very elegant (to the point that I misguidedly used it in the SSQL egg), but there’s of course also coops etc if you want something more fancy.
I’ve used coops a lot ♥ and of course tinyclos back in the day of C3.
A lot of my love for objects were out of the support for generic methods. These days since I have my match-generics I can get by with just records or call-tables most of the time.
That’s the beauty of Scheme, you can just create (or pick) the exact best thing for your use case. Whereas with your Java example you were basically stuck with what the language offered (unless you went the route of generating code)
Which I did, and I used Chicken to generate the code (both Java code and matching XSLT code) ♥
hehe, epic!
Python is not a great language for demonstrating this though because you have to use nonlocal to assign to any closure variables. Going further, objects, closures, and just plain old passing a lot of arguments are all roads to the same end.
You’re 100% right.
I gave a talk about this symmetry at BOB 2020 in Berlin. I’m linking the org-mode slides here in case people are interested. They discuss the symmetry in practice, and also some theoretical / more advanced elements.
Something that people often don’t recognize is that, while the Expression Problem suggests that OO programs are make it easier to add new data cases, while FP make it easier to add new operations (without modifying existing code), in practice in my experience readability is also affected: it’s easier to read code where all the logic is in the same place, so FP code makes operations easier to read and OO code makes “data cases” easier to read. I think this has potential consequence for code visualization tools.
Omigosh thanks for putting this amazing material out there. Also your second paragraph is blowing my mind. Amazing!
Yes, if you have lots of types and few methods, OO wins; if you have many methods and very few types, imperative (or FP) wins. Where both break down are those cases where you have lots of types (or objects) and lots of methods.
This is very easy to show in Common Lisp:
Absolutely, as a Lisper myself, I had to swallow plenty of pride to use Python for this particular example. I quite like the code you posted in this comment.
Using closures to implement objects that respond to messages is also famously done in SICP. If you take this to its limit, you can implement a full class-based object system (even with a meta object protocol, if you like) using only closures.