Far from being mutually exclusive ways to write software, OOP and functional programming are two useful mental toolsets for designing software. Graham explores how the two are related to find out more about each.
10. Digression: 2 > 1
⇔
Count: ℕ
At: ℕ→T?
<T>
struct List<T> {
let count: () -> Int
let at: (Int) -> T?
}
protocol List {
associatedtype T
func count() -> Int
func at(index: Int) -> T?
}
⇔
11. struct List<T> {
let count: () -> Int
let at: (Int) -> T?
}
protocol List {
associatedtype T
func count() -> Int
func at(index: Int) -> T?
}
⇔
count f(𝑥)
at g(𝑥)
13. enum ListSelectors {
case count
case at
}
enum ListIMPs<T> {
case count (() -> Int)
case at ((Int) -> T?)
}
typealias List<T> = (ListSelectors) -> ListIMPs<T>
14. If 2>1, then ∞>2
typealias Selector = String
typealias Object = (Selector) -> IMP
enum IMP {
case accessor(()->(Object?)
case mutator((Object)->Void)
…
}
func swift_msgSend(this: Object, _cmd: Selector) -> IMP
{
return this(_cmd)
}
15. TL;DR
• Objects are functions
• …that close over their ivars (constructors are HOFs)
• …that map method selectors to method implementations
• …they’re just ways to pick functions at runtime
Sample code with more discussion: https://github.com/iamleeg/OOPInFPInSwift
Hinweis der Redaktion
The Smalltalk version of OOP is derived from adding dynamic binding—that is, late choice of function—to LISP. OOP is not much more than higher-order functions, closures and appropriate use of types, let’s take a look.
Let’s go on a journey. We’re going to start with a function definition, then we’re going to combine functions to describe types with multiple operations, then we’re going to introduce dynamic dispatch to choose functions at runtime. What we’ll end up with is an object that, for now, I’ll call an object.
A set is equivalent to the predicate that tests whether an object is a member of the set. This is a specific case of my assertion: you can define something (usefully, if not exactly) using only its externally-observable behaviour; the operations it supports and its behaviour under those operations.
If I have a table where I can look up an object and find out whether it is or is not a duck, philosophers would call this an extensional definition of the set of ducks. If I have a predicate that I can pass an object and get true if it is a duck and false otherwise, they would call this an intensional definition of the set of ducks. Importantly, both of these are definitions of the same set, they are equivalent.
Someone who knows Swift will probably say that this protocol and struct are not interchangeable because of the difference between associated types and generic types. I don’t care: from a “thinking about this type” perspective, they are each product types of a count function and a type-dependent at function.
In either case, the protocol or the structure, we have a table of names that we can use to look up functions. In other words, we can select methods based on their name, by looking up the name in the table.
…and we have seen before, with sets, that where we have a table we can replace it with a function. So in this case we could have some function that takes the name of a method (the selector), and returns the method implementation. What would that look like?
A method implementation (IMP) is the sum type of all the possible method signatures (two, in this case). This is in principle true in Objective-C, which is why it has the NSMethodSignature type to know how to find the parameters and return value of a method. Smalltalk and Ruby have a single calling convention (and a single type) so can ignore the type of an IMP.
This message send function is more general than that in Smalltalk, ObjC, JS, Ruby etc because it gives objects agency: the object itself decides how to choose what method to give you.