├── 1Sets.playground ├── Contents.swift ├── contents.xcplayground └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ └── grahamlee.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── 2Lists.playground ├── Contents.swift ├── contents.xcplayground └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ └── grahamlee.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── 3DynamicDispatch.playground ├── Contents.swift ├── contents.xcplayground └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ └── grahamlee.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── 4ArbitrarySelectors.playground ├── Contents.swift └── contents.xcplayground └── README /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 | -------------------------------------------------------------------------------- /1Sets.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1Sets.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/117a74affdb04c5e47e94340abfb0e304cad03cd/1Sets.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /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.. 2 | 3 | 4 | -------------------------------------------------------------------------------- /2Lists.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /2Lists.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamleeg/OOPInFPInSwift/117a74affdb04c5e47e94340abfb0e304cad03cd/2Lists.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /3DynamicDispatch.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /3DynamicDispatch.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /3DynamicDispatch.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamleeg/OOPInFPInSwift/117a74affdb04c5e47e94340abfb0e304cad03cd/3DynamicDispatch.playground/playground.xcworkspace/xcuserdata/grahamlee.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /4ArbitrarySelectors.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | // in episode 3, we replaced the selector tables in our list type with dispatch functions that took a 4 | // selector as an argument and returned a method implementation. However, we did not address a big 5 | // shortcoming that we found in episode 2: lists are defined as "something that has a `count` method and 6 | // an `at` method", and anything else - including things that have a `count` method, an `at` method, and 7 | // a `describe` method - is not a list. 8 | 9 | // the dispatch tables from episode 2, and the dispatch functions from episode 3, are _total_ functions 10 | // over a limited space of possible selectors. In this episode, we'll expand the space of selectors to 11 | // be any string 12 | 13 | typealias Selector = String 14 | 15 | // and we'll make method dispatch a _partial_ function so that we don't have to supply all infinity 16 | // methods. As with episode 3, there are multiple possible method signatures for the method at a given 17 | // selector, so we'll build a discriminated union of possible implementation types. 18 | 19 | enum IMP { 20 | case accessor(()->((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 | -------------------------------------------------------------------------------- /4ArbitrarySelectors.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------