├── 1Sets.playground
├── contents.xcplayground
├── playground.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── grahamlee.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── Contents.swift
├── 2Lists.playground
├── contents.xcplayground
├── playground.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── grahamlee.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── Contents.swift
├── 3DynamicDispatch.playground
├── contents.xcplayground
├── playground.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── grahamlee.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── Contents.swift
├── 4ArbitrarySelectors.playground
├── contents.xcplayground
└── Contents.swift
└── README
/1Sets.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/2Lists.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/1Sets.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/2Lists.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/3DynamicDispatch.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/4ArbitrarySelectors.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/3DynamicDispatch.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/1Sets.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamleeg/OOPInFPInSwift/HEAD/1Sets.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/2Lists.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamleeg/OOPInFPInSwift/HEAD/2Lists.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/3DynamicDispatch.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamleeg/OOPInFPInSwift/HEAD/3DynamicDispatch.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | # OOPInFPInSwift
2 |
3 | This is a collection of playgrounds in which object-oriented
4 | programming is built out of functions in Swift. It supplants
5 | [Objective-Swift](https://github.com/iamleeg/Objective-Swift), which
6 | was written such a long time ago (two years) that everything is broken
7 | and horrible. LOL computers.
8 |
9 | These playgrounds were made in Xcode 9, so if WWDC 2018 has already
10 | happened you probably can't use these any more either.
11 |
12 | ## Usage
13 |
14 | Load the playgrounds in Xcode. They have descriptive comments.
15 |
16 | ## Licence
17 |
18 | Unless overridden by a licence grant in the source files, all of the
19 | sources in this repository are Copyright (C) 2017 Graham Lee and
20 | licensed to you under the terms of the [MIT
21 | License](https://opensource.org/licenses/MIT).
22 |
--------------------------------------------------------------------------------
/1Sets.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | // let's define a set as a container in which an object is either present or absent.
4 | // here's a definition of what a set of integers might look like:
5 | typealias IntegerSet = (Int) -> Bool
6 | typealias MySet = (T) -> Bool
7 |
8 | // so these two things are both examples, or instances, of the "Set of Integers" idea
9 | let emptySet : MySet = {(_) in return false}
10 | let universe : MySet = {(_) in return true}
11 |
12 | emptySet(12)
13 | universe(12)
14 |
15 | // but they're pretty unexciting. it'd be nice to have some _configurable_ Sets, so we
16 | // can decide what is or is not in a Set. We'll create a function that returns sets, and
17 | // we can configure them by passing parameters to the functions.
18 |
19 | func RangeSet(from lower: T, to upper: T) -> MySet
20 | {
21 | return { (x) in (x >= lower) && (x <= upper)}
22 | }
23 |
24 | // notice that instances of RangeSet are implemented as _closures_ over the parameters in
25 | // the constructor.
26 |
27 | let threeFourFive : IntegerSet = RangeSet(from: 3, to: 5)
28 |
29 | threeFourFive(2)
30 | threeFourFive(3)
31 |
32 | let oneAndTwo : IntegerSet = RangeSet(from: 1, to: 2)
33 |
34 | // we can build set definitions out of other sets, too
35 | // notice here that the two sets escape the constructor, not the instance: we have
36 | // instance variable encapsulation
37 | func UnionSet(of left: @escaping MySet,
38 | and right: @escaping MySet) -> MySet
39 | {
40 | return { (x) in (left(x) || right(x))}
41 | }
42 |
43 | let oneToFive : IntegerSet = UnionSet(of: oneAndTwo, and: threeFourFive)
44 |
45 | oneToFive(0)
46 | oneToFive(6)
47 | oneToFive(2)
48 |
49 | let twoToFour : IntegerSet = RangeSet(from: 2, to: 4)
50 |
51 | func IntersectionSet(of left: @escaping MySet, and right: @escaping MySet) -> MySet
52 | {
53 | return { (x) in (left(x) && right(x)) }
54 | }
55 |
56 | // because our sets are all the same type but respond in the same way, we can use
57 | // different "types" of sets anywhere a set is expected.
58 | // This is polymorphism.
59 | let intersectional = IntersectionSet(of: oneToFive, and: twoToFour)
60 |
61 | intersectional(2)
62 | intersectional(1)
63 |
64 |
--------------------------------------------------------------------------------
/3DynamicDispatch.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | // the Sets in episode one of this series had a single method, the membership test, so were defined
4 | // exactly as a function that implemented the membership test.
5 |
6 | // in episode two, Lists had two methods: counting and retrieval, so we used structures to implement
7 | // tables that contained these two methods. This turned out to be quite cumbersome when we wanted
8 | // to extend lists, because we had to create a different table with the two+others methods we now needed
9 | // and some machinery to map between them.
10 |
11 | // the functional programming feature for mapping between two things is a "function", so let's stop using
12 | // tables and start using functions. We'll take baby steps, starting by just changing the table into a
13 | // function. The selector mapping for a List could look like this:
14 |
15 | enum ListSelectors {
16 | case count
17 | case at
18 | }
19 |
20 | // now rather than get a table containing _all_ methods, we'll be able to call a function with a selector
21 | // get a _specific_ method. Notice that `count` and `at` have different signatures, so the selector map
22 | // actually needs to be a union type: we need to understand the method signature for a selector
23 |
24 | enum MyList {
25 | case count (() -> Int)
26 | case at ((Int) -> T?)
27 | }
28 |
29 | // now an object can be written as a message dispatch function: given a message containing a selector
30 | // find the method that corresponds to the selector.
31 |
32 | func EmptyList(_cmd: ListSelectors) -> MyList
33 | {
34 | switch _cmd {
35 | case .count:
36 | return MyList.count({ return 0 })
37 | case .at:
38 | return MyList.at({ _ in return nil })
39 | }
40 | }
41 |
42 | // because Swift requires you to use you're type's good, you need to make sure the type of the thing
43 | // you extract from this discriminated union matches the type you put in. This makes invoking the
44 | // returned method quite cumbersome:
45 |
46 | let emptyCountIMP : MyList = EmptyList(_cmd: .count)
47 | switch emptyCountIMP {
48 | case .count(let f):
49 | f()
50 | default:
51 | assertionFailure("Method signature does not match expected type for selector")
52 | }
53 |
54 | // Doing that every time would be tedious, so rather than make clients do message lookups and invoke the
55 | // messages themselves over and over, we'll give them a "message send" syntax which takes care of dispatching
56 | // the selector, making sure the method signature matches and invoking the discovered method.
57 | func countOfList(list: ((ListSelectors)->MyList)) -> Int
58 | {
59 | switch list(.count) {
60 | case .count(let f):
61 | return f()
62 | default:
63 | assertionFailure("Method signature does not match expected type for selector")
64 | // unreached
65 | return 0
66 | }
67 | }
68 |
69 | func objectInListAtIndex(list: ((ListSelectors)->MyList), index: Int) -> T?
70 | {
71 | switch list(.at) {
72 | case .at(let f):
73 | return f(index)
74 | default:
75 | assertionFailure("Method signature does not match expected type for selector")
76 | // unreached
77 | return nil
78 | }
79 | }
80 |
81 | // so, given a linked list built from this system
82 | func MyLinkedList(head: T, tail: @escaping ((ListSelectors)->MyList)) -> ((ListSelectors) -> MyList)
83 | {
84 | return {(_cmd) in
85 | switch _cmd {
86 | case .count:
87 | return MyList.count({
88 | return 1 + countOfList(list: tail)
89 | })
90 | case .at:
91 | return MyList.at({ (i) in
92 | if i < 0 { return nil }
93 | if i == 0 { return head }
94 | return objectInListAtIndex(list: tail, index: i - 1)
95 | })
96 | }
97 | }
98 | }
99 |
100 | let unitList = MyLinkedList(head: "Hello",
101 | tail: MyLinkedList(head: "World",
102 | tail: EmptyList))
103 |
104 | // we can easily use its methods via the type-safe dispatch functions
105 | countOfList(list: unitList)
106 | objectInListAtIndex(list: unitList, index: 1)
107 |
108 |
--------------------------------------------------------------------------------
/2Lists.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | // what we saw in episode 1 was that sets can be defined as a single operation: given an element of
4 | // the set's type, is that element a member of the set? That let us create a polymorphic set type,
5 | // as anything with the signature (Element)->Bool is a set.
6 |
7 | // But what if we have more than one operation? Consider a list, which has a count of elements and
8 | // a way to retrieve an element in a given position. Now the definition of a list instance is not a
9 | // single function, it's _two_ functions: the count and the retrieval function. Let's package those
10 | // up into a structure: anything that is an instance of that structure is a list, as far as we're
11 | // concerned.
12 |
13 | struct List {
14 | let count: () -> Int
15 | let at: (Int) -> T?
16 | }
17 |
18 | // the members of the structure are _selectors_: given a list, we can "select" its count method or
19 | // its at method for execution. Here's a constructor for a boring list
20 |
21 | func EmptyList() -> List
22 | {
23 | return List(count: {return 0}, at: {_ in return nil})
24 | }
25 |
26 | let noIntegers: List = EmptyList()
27 |
28 | noIntegers.count()
29 | noIntegers.at(0)
30 |
31 | // as with sets, we can build lists that close over their constructor parameters as instance variables.
32 | // the easiest way to build a list of arbitrary length is to build one with two members.
33 | func LinkedList(head: T, tail: List) -> List
34 | {
35 | return List(count: { return 1 + tail.count() },
36 | at: {index in return (index == 0) ? head : tail.at(index - 1)})
37 | }
38 |
39 | let oneTwoThree : List = LinkedList(head: 1, tail: LinkedList(head: 2, tail: LinkedList(head: 3, tail: EmptyList())))
40 |
41 | oneTwoThree.count()
42 | oneTwoThree.at(0)
43 | oneTwoThree.at(1)
44 |
45 | // this is all very well, but imagine that I want to add a new behaviour to my lists, for example the
46 | // ability to produce a description. I can add that method to the list description:
47 |
48 | struct DescribableList {
49 | let count: () -> Int
50 | let at: (Int) -> T?
51 | let describe: () -> String
52 | }
53 |
54 | // but do I really want to go back and change every List I've ever created to make it a describable list?
55 | // let's look at an alternative: delegation. We'll make a selector table in which the `count` and `at`
56 | // selectors are _optional_: a DescribableList object can supply those methods, or rely on some other
57 | // object supplying them, such as an existing list.
58 |
59 | struct DescribableListSelectors {
60 | let count: (() -> Int)?
61 | let at: ((Int) -> T?)?
62 | let describe: () -> String
63 | }
64 |
65 | // and a constructor that takes a list instance and adds a description, optionally also overriding
66 | // the `count` and `at` methods. We don't _need_ to override them, we can _inherit_ them from the list.
67 |
68 | func ListSubtypeByAddingDescription(prototype: List,
69 | overrides: DescribableListSelectors) -> DescribableList
70 | {
71 | let countImplementation: () -> Int = overrides.count ?? prototype.count
72 | let atImplementation: (Int) -> T? = overrides.at ?? prototype.at
73 |
74 | return DescribableList(count: countImplementation,
75 | at: atImplementation,
76 | describe: overrides.describe)
77 | }
78 |
79 | // so to add a description method to a list, we use that list as the prototype
80 | func ListOfStrings(strings: List) -> DescribableList
81 | {
82 | let describe: () -> String = {
83 | var output = ""
84 | for i in 0..((Selector)->IMP)?)
21 | case asInteger(()->Int?)
22 | case methodMissing(()->((Selector)->IMP)?)
23 | case mutator((((Selector)->IMP))->Void)
24 | case description(()->String?)
25 | }
26 |
27 | // most of these cases exist so that we can move data between this system and the Swift type system,
28 | // with one important exception.
29 |
30 | // Notice the repeated signature `(Selector)->IMP`. That comes up a few times: accessors and mutators treat
31 | // that type as their return value and argument respectively, and also it can be returned in the case of
32 | // a missing method, to allow the Selector to be passed to a handler that can forward the message or
33 | // otherwise deal with it.
34 |
35 | // My assertion is that what we're going to build in this episode are functions that turn selectors into
36 | // method implementations. They can work with references to other functions of that type. The grand claim
37 | // that this whole series is predicated around is the following type alias:
38 |
39 | typealias Object = (Selector) -> IMP
40 |
41 | // in other words, an object is nothing other than a function that maps messages onto methods.
42 |
43 | // here's the empty object constructor.
44 | func DoesNothing() -> Object {
45 | var _self : Object! = nil
46 | func myself (selector: Selector)->IMP {
47 | return IMP.methodMissing({assertionFailure("method missing: \(selector)"); return nil;})
48 | }
49 | _self = myself
50 | return _self
51 | }
52 |
53 | let o : Object = DoesNothing()
54 |
55 | // here's something a bit more interesting: an object that captures a Swift `Int` and makes it part of our
56 | // object system. It provides a method for retrieving the `Int`.
57 |
58 | func Integer(x: Int, proto: @escaping Object) -> Object {
59 | var _self : Object! = nil
60 | let _x = x
61 | func myself(selector:Selector) -> IMP {
62 | switch(selector) {
63 | case "intValue":
64 | return IMP.asInteger({ return _x })
65 | case "description":
66 | return IMP.description({ return "\(_x)" })
67 | default:
68 | return IMP.methodMissing({ return proto })
69 | }
70 | }
71 | _self = myself
72 | return _self
73 | }
74 |
75 | let theMeaning = Integer(x:42, proto:o)
76 |
77 | // we'll come back to that `proto` parameter shortly. Meanwhile, remember that in the previous episode we
78 | // had to unpack all those enumerated types and discriminated unions. We have to do that here, too. Let's
79 | // build the "message lookup" operator, which returns the method implementation for a given selector.
80 | // If the receiving object doesn't implement the selector, it should return a `methodMissing` implementation
81 | // which can tell us another object to ask.
82 |
83 | infix operator ..
84 |
85 | func .. (receiver: Object?, _cmd:Selector) -> IMP? {
86 | if let this = receiver {
87 | let method = this(_cmd)
88 | switch(method) {
89 | case .methodMissing(let f):
90 | return f().._cmd
91 | default:
92 | return method
93 | }
94 | }
95 | else {
96 | return nil
97 | }
98 | }
99 |
100 | // so now we can look up methods on objects. Like Objective-C, if the receiver is `nil`, then the result
101 | // is `nil` (unlike Objective-C, that `nil` is the empty value of the target type, not a magic way of
102 | // handling a 0)
103 | nil.."intValue"
104 |
105 | // notice that the `Integer` we created up there has a `proto` instance variable, and any method it doesn't
106 | // understand gets passed to `proto`. That's because this object system is more like Self or JavaScript than
107 | // Smalltalk or Objective-C: it doesn't have classes, but you can implement shared behaviour by putting it
108 | // in a prototype object (that's prototype in the inheritance sense, not the cloning sense).
109 | // You can build classes out of prototypes, but I'll leave that for you.
110 |
111 | // back to the machinery that removes the boilerplate of unboxing all those enums, it could look like this:
112 | func ℹ︎(receiver:Object?, _cmd:Selector)->Int? {
113 | if let imp = receiver.._cmd {
114 | switch(imp) {
115 | case .asInteger(let f):
116 | return f()
117 | default:
118 | return nil
119 | }
120 | } else {
121 | return nil
122 | }
123 | }
124 |
125 | ℹ︎(receiver: theMeaning, _cmd: "intValue")
126 |
127 | // notice that we're going to need a different function like ℹ︎ for every type in the `IMP` enum, which you
128 | // won't be used to from other OOP systems. In Smalltalk, Ruby, and similar, everything is an object
129 | // so the question of "what type is it" doesn't arise. In something like Objective-C, it _does_ arise,
130 | // and is handled internally (though imperfectly) by the runtime library. In Swift we have to play within
131 | // its type system so we have to explicitly cope with whatever types we've created.
132 |
133 | func 🖨(receiver:Object?, _cmd:Selector)->String? {
134 | if let imp = receiver.._cmd {
135 | switch(imp) {
136 | case .description(let f):
137 | return f()
138 | default:
139 | return nil
140 | }
141 | } else {
142 | return nil
143 | }
144 | }
145 |
146 | 🖨(receiver: theMeaning, _cmd:"description")
147 |
148 |
--------------------------------------------------------------------------------