├── .gitignore ├── LICENSE ├── README.md └── SwiftAndLogic.playground ├── Pages ├── ForAll and Exists.xcplaygroundpage │ └── Contents.swift ├── Gentzen Natural Deduction.xcplaygroundpage │ └── Contents.swift └── Hilbert Style (Combinator).xcplaygroundpage │ └── Contents.swift ├── Sources └── Types.swift └── contents.xcplayground /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yasuhiro Inami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftAndLogic 2 | Sample code for iOSDC 2019 and NSSpain 2019 3 | 4 | ## References 5 | 6 | - 🇯🇵 [iOSDC Japan 2019](https://fortee.jp/iosdc-japan-2019/proposal/9529444c-a849-47cd-94a7-bf2b694320aa) (Japanese) 7 | - Slide: [Swiftプログラミングと論理 〜そして帰ってきた圏論〜 / Swift and Logic, and Category Theory \- Speaker Deck](https://speakerdeck.com/inamiy/swift-and-logic-and-category-theory) 8 | - Video: TBD 9 | - 🇪🇸 [NSSpain 2019](https://2019.nsspain.com/) 10 | - Slide: [Swift Programming and Logic \- Speaker Deck](https://speakerdeck.com/inamiy/swift-programming-and-logic) 11 | - Video: [Swift Programming and Logic \- Yasuhiro Inami on Vimeo](https://vimeo.com/362179375) 12 | -------------------------------------------------------------------------------- /SwiftAndLogic.playground/Pages/ForAll and Exists.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // MARK: - (A_1 → B) ∧ (A_2 → B) ∧ ⋯ ≅ ∀X.(X → B) 2 | 3 | let intToBool: (Int) -> Bool = { _ in true } 4 | let floatToBool: (Float) -> Bool = { _ in true } 5 | let strToBool: (String) -> Bool = { _ in true } 6 | // ... 7 | 8 | // Tons of same impl... Can we abstract this? 9 | // ... Yes, use generic function! 10 | 11 | func xToBool(_ x: X) -> Bool { true } 12 | 13 | // MARK: - ∀X.(X → B) ≅ (∃X.X) → B 14 | 15 | func forallXToBool(_ x: X) -> Bool { 16 | anyToBool(x) 17 | } 18 | 19 | /// - Note: `∃X.X` is isomorphic to `Any`. 20 | func anyToBool(_ any: Any) -> Bool { 21 | forallXToBool(any) 22 | } 23 | 24 | // MARK: - ∀(X <: P).(X → B) ≅ (∃(X <: P).X) → B 25 | 26 | protocol P { 27 | var value: String { get } 28 | } 29 | 30 | struct AnyP: P { // Type erasure 31 | let value: String 32 | } 33 | 34 | func forallXProtocolToBool(_ x: X) -> Bool { // Generic func 35 | protocolToBool(x) 36 | } 37 | 38 | func protocolToBool(_ p: P) -> Bool { // Protocol func (dynamic) 39 | forallXProtocolToBool(AnyP(value: p.value)) 40 | } 41 | 42 | // Reverse Opaque Result Type (static, isn't supported yet in Swift 5.1) 43 | // ERROR: 'some' types are only implemented for the declared type of properties and subscripts and the return type of functions 44 | //func someProtocolToBool(_ p: some P) -> Bool { 45 | // forallXProtocolToBool(p) 46 | //} 47 | 48 | 49 | 50 | print("✅") 51 | -------------------------------------------------------------------------------- /SwiftAndLogic.playground/Pages/Gentzen Natural Deduction.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // NOTE: 2 | // - Please use Xcode 11 (Swift 5.1). 3 | // - `X ===> Y` is an inference (transformation) rule from premise X to conclusion Y. 4 | // - `X ⊢ Z` means `X ===> ... ===> Z` (derivable from using axioms and inference rules). 5 | 6 | //-------------------------------------------------------------------------------- 7 | 8 | // MARK: → (imply) 9 | 10 | /// `B ===> A → B` 11 | /// - Note: More formally, A ⊢ B ===> ⊢ A → B (Deduction Theorem). 12 | func implyIntro(_ b: B) -> ((A) -> B) { 13 | { _ in b } 14 | } 15 | 16 | /// `A → B, A ===> B` (Modus ponens) 17 | func implyElim(_ a: A, _ f: (A) -> B) -> B { 18 | f(a) 19 | } 20 | 21 | // MARK: ∧ (conjunction, and) 22 | 23 | /// `A, B ===> A ∧ B` 24 | func andIntro(_ a: A, _ b: B) -> (A, B) { 25 | (a, b) 26 | } 27 | 28 | /// `A ∧ B ===> A` 29 | func andElim1(_ ab: (A, B)) -> A { 30 | ab.0 31 | } 32 | 33 | /// `A ∧ B ===> B` 34 | func andElim2(_ ab: (A, B)) -> B { 35 | ab.1 36 | } 37 | 38 | // MARK: ∨ (disjunction, or) 39 | 40 | /// `A ===> A ∨ B` 41 | func orIntro1(_ a: A) -> Either { 42 | .left(a) 43 | } 44 | 45 | /// `B ===> A ∨ B` 46 | func orIntro2(_ b: B) -> Either { 47 | .right(b) 48 | } 49 | 50 | /// `A ∨ B, A → C, B → C ===> C` 51 | func orElim(_ e: Either, _ ac: (A) -> C, _ bc: (B) -> C) -> C { 52 | switch e { 53 | case let .left(a): 54 | return ac(a) 55 | case let .right(b): 56 | return bc(b) 57 | } 58 | } 59 | 60 | // MARK: ¬ (not) 61 | 62 | /// `A ⊢ ⊥ ===> ⊢ ¬A` 63 | func notIntro(_ x: Never) -> Not { 64 | Not { _ in x } // compiles in Swift 5.1 65 | } 66 | 67 | /// `A, ¬A ===> ⊥` (NOTE: not using `fatalError`) 68 | func notElim(_ a: A, _ notA: Not) -> Never { 69 | notA.f(a) 70 | } 71 | 72 | // NOTE: Above → ∧ ∨ ¬ introduction / elimination rules are so called "Minimal Logic". 73 | 74 | //-------------------------------------------------------------------------------- 75 | 76 | // MARK: ⊥ (bottom) 77 | 78 | // NOTE: Adding `absurd` makes "minimal logic" into "Intuitionistic Logic (IL)". 79 | 80 | /// `⊥ ===> A` 81 | func absurd(_ x: Never) -> A { 82 | // Do nothing, but it compiles in Swift 5.1 83 | } 84 | 85 | /// `A ===> ¬¬A` 86 | func doubleNegationIntro(_ a: A) -> Not> { 87 | Not { notA in 88 | notA.f(a) 89 | } 90 | } 91 | 92 | // MARK: Triple Negation ≣ Single Negation 93 | 94 | /// `¬¬¬A ===> ¬A` 95 | func tripleNegationToSingle(_ notNotNotA: Not>>) -> Not { 96 | Not { a in 97 | notNotNotA.f(Not> { notA in 98 | notA.f(a) 99 | }) 100 | } 101 | } 102 | 103 | /// `¬A ===> ¬¬¬A` 104 | func singleNegationToTriple(_ notA: Not) -> Not>> { 105 | Not>> { notNotA in 106 | notNotA.f(notA) 107 | } 108 | } 109 | 110 | // MARK: De-Morgan's Law (except `¬(A ∧ B) ===> ¬A ∨ ¬B`) 111 | 112 | /// `¬(A ∨ B) ===> ¬A ∧ ¬B` 113 | func deMorgan(_ notEither: Not>) -> (Not, Not) { 114 | (Not { a in notEither.f(.left(a)) }, Not { b in notEither.f(.right(b)) }) 115 | } 116 | 117 | /// `¬A ∧ ¬B ===> ¬(A ∨ B)` 118 | func deMorgan2(_ notAB: (Not, Not)) -> Not> { 119 | Not { either in 120 | switch either { 121 | case let .left(a): 122 | return notAB.0.f(a) 123 | case let .right(b): 124 | return notAB.1.f(b) 125 | } 126 | } 127 | } 128 | 129 | /// `¬A ∨ ¬B ===> ¬(A ∧ B)` 130 | func deMorgan3(_ either: Either, Not>) -> Not<(A, B)> { 131 | Not { ab in 132 | switch either { 133 | case let .left(notA): 134 | return notA.f(ab.0) 135 | case let .right(notB): 136 | return notB.f(ab.1) 137 | } 138 | } 139 | } 140 | 141 | //-------------------------------------------------------------------------------- 142 | 143 | // MARK: Classical Logic (Forbidden functions in Intuitionistic Logic / Swift) 144 | 145 | // NOTE: Adding `absurd` makes "minimal logic" into "intuitionistic logic". 146 | 147 | /// `A ∨ ¬A` (Law of excluded middle) 148 | func excludedMiddle() -> Either> { 149 | fatalError("Can't impl in Intuitionistic Logic") 150 | } 151 | 152 | /// `¬¬A ===> A` (Double negation elimination) 153 | /// `¬A ⊢ ⊥ ===> A` (Reductio ad absurdum) 154 | func doubleNegationElim(_ notNotA: Not>) -> A { 155 | fatalError("Can't impl in Intuitionistic Logic") 156 | 157 | // NOTE: Can still impl when using `excludedMiddle()`, though it is not still valid. 158 | let either: Either> = excludedMiddle() 159 | switch either { 160 | case let .left(a): 161 | return a 162 | case let .right(notA): 163 | return absurd(notNotA.f(notA)) 164 | } 165 | } 166 | 167 | /// `¬(A ∧ B) ===> ¬A ∨ ¬B` 168 | func deMorgan4(_ notAB: Not<(A, B)>) -> Either, Not> { 169 | fatalError("Can't impl in Intuitionistic Logic") 170 | 171 | // NOTE: This compiles, but using invalid `excludedMiddle()`. 172 | let either: Either> = excludedMiddle() 173 | switch either { 174 | case let .left(a): 175 | return .right(Not { b in notAB.f((a, b)) }) 176 | case let .right(notA): 177 | return .left(notA) 178 | } 179 | } 180 | 181 | /// `((A → B) → A) → A` 182 | func peirceLaw() -> (((A) -> B) -> A) -> A { 183 | { (aba: ((A) -> B) -> A) in 184 | let ab: (A) -> B = { fatalError("Can't impl in Intuitionistic Logic") }() 185 | let a = aba(ab) 186 | return a 187 | 188 | // NOTE: This compiles, but using invalid `excludedMiddle()`. 189 | let either: Either> = excludedMiddle() 190 | switch either { 191 | case let .left(a): 192 | return a 193 | case let .right(notA): 194 | return aba({ a in absurd(notA.f(a)) }) 195 | } 196 | } 197 | } 198 | 199 | //-------------------------------------------------------------------------------- 200 | 201 | // MARK: Double-negation translation (Glivenko’s Theorem) 202 | 203 | // NOTE: 204 | // "_IL" stands for "Intuitionistic Logic", which can be translated from classical logic by 205 | // adding [Double\-negation translation](https://ncatlab.org/nlab/show/double+negation+translation) 206 | 207 | /// `¬¬(A ∨ ¬A)` (Law of excluded middle in Intuitionistic Logic) 208 | /// https://github.com/vladciobanu/logic-in-haskell/blob/master/src/Logic.hs 209 | func excludedMiddle_IL() -> Not>>> { 210 | Not>>> { notEither in 211 | let notA: Not = Not { a in notEither.f(.left(a)) } 212 | let either: Either> = .right(notA) 213 | return notEither.f(either) 214 | } 215 | } 216 | 217 | /// `¬¬¬¬A ===> ¬¬A` (Double negation elimination in Intuitionistic Logic) 218 | func doubleNegationElim_IL(_ notNotNotNotA: Not>>>) -> Not> { 219 | Not> { notA in 220 | notNotNotNotA.f( 221 | Not>> { notNotA in 222 | notNotA.f(notA) 223 | } 224 | ) 225 | } 226 | } 227 | 228 | /// `¬¬¬(A ∧ B) ===> ¬¬(¬A ∨ ¬B)` (De-Morgan's Law in Intuitionistic Logic) 229 | func deMorgan4_IL(_ notNotNotAB: Not>>) -> Not, Not>>> { 230 | Not, Not>>> { (notEither: Not, Not>>) in 231 | let notNotAB = Not> { notAB in 232 | let notB = Not { b in 233 | let notA = Not { a in 234 | notAB.f((a, b)) 235 | } 236 | let either: Either, Not> = .left(notA) 237 | return notEither.f(either) 238 | } 239 | let either: Either, Not> = .right(notB) 240 | return notEither.f(either) 241 | } 242 | return notNotNotAB.f(notNotAB) 243 | } 244 | } 245 | 246 | /// `¬¬((A → B) → A) → ¬¬A` (Peirce's Law in Intuitionistic Logic) 247 | func peirceLaw_IL() -> (Not B) -> A>>) -> Not> { 248 | { notNotF in 249 | Not> { notA in 250 | let notABA = Not<((A) -> B) -> A> { aba in 251 | let ab: (A) -> B = { a in absurd(notElim(a, notA)) } 252 | let a = aba(ab) 253 | return notA.f(a) 254 | } 255 | return notNotF.f(notABA) 256 | } 257 | } 258 | } 259 | 260 | //-------------------------------------------------------------------------------- 261 | 262 | // MARK: - callCC (Call with Current Continuation) 263 | 264 | /// Continuation. 265 | typealias Cont = (@escaping (A) -> R) -> R 266 | 267 | func flatMap(_ ma: @escaping Cont, _ f: @escaping (A) -> Cont) -> Cont { 268 | { br in 269 | ma { a in f(a)(br) } 270 | } 271 | } 272 | 273 | /// Call with Current Continuation. 274 | /// 275 | /// - Peirce (classical): `((A → B) → A) → A` 276 | /// - CallCC (intuitionistic): `((A → M) → M) → M` 277 | /// 278 | /// - Note: `callCC` is like control operators e.g. `goto`, `longjmp`, `return`, `throw`, `break`. 279 | func callCC( 280 | _ f: @escaping (_ exit: @escaping (A) -> Cont) -> Cont 281 | ) -> Cont 282 | { 283 | { outer in 284 | f { a in { _ in outer(a) } }(outer) 285 | } 286 | } 287 | 288 | /// - DoubleNegationElim (classical): `((A → ⊥) → ⊥) ===> A` 289 | /// - DoubleNegationElim (intuitionistic): `((A → M<⊥>) → M<⊥>) ===> M` 290 | func doubleNegationElim_callCC( 291 | _ doubleNegation: @escaping (_ neg: @escaping (A) -> Cont) -> Cont 292 | ) -> Cont 293 | { 294 | return callCC { exit -> Cont in 295 | flatMap(doubleNegation(exit), absurd) 296 | } 297 | } 298 | 299 | //-------------------------------------------------------------------------------- 300 | 301 | // MARK: - CPS (Continuation Passing Style) 302 | 303 | /// `A → ((A → B) → B)` 304 | /// Also, `⊢(CL) A ===> ⊢(IL) ((A → B) → B)` (CL = classical logic) 305 | /// Also, `⊢(CL) A ===> ⊢(IL) ¬¬A` (where `B = ⊥`, Glivenko's theorem) 306 | func toCPS(_ a: A) -> (((A) -> B) -> B) { 307 | { ab in ab(a) } 308 | } 309 | 310 | /// `((A → B) → B) → A` 311 | /// - Note: Not type-safe in Swift due to lack of Rank2 polymorphism. 312 | func fromCPS(_ f: @escaping ((A) -> Any) -> Any) -> A { 313 | f({ $0 } as (A) -> Any) as! A 314 | } 315 | 316 | /// `X → A <===> ∀B.((A → B) → (X → B))` 317 | /// Also, `X → A ===> (A → ⊥) -> (X → ⊥)) = ¬A → ¬X` (contraposition) 318 | func cpsTransform(_ f: @escaping (X) -> A) 319 | -> ((@escaping (A) -> B) -> ((X) -> B)) 320 | { 321 | { g in { x in g(f(x)) } } 322 | } 323 | 324 | 325 | 326 | print("✅") 327 | -------------------------------------------------------------------------------- /SwiftAndLogic.playground/Pages/Hilbert Style (Combinator).xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /// `S = (A → (B → C)) → ((A → B) → (A → C))` 2 | func s(_ a: @escaping (C) -> (A) -> B) 3 | -> (_ b: @escaping (C) -> A) 4 | -> (C) -> B 5 | { 6 | { b in { c in a(c)(b(c)) } } 7 | } 8 | 9 | /// `K = A → (B → A)` 10 | func k(_ a: A) -> (B) -> A { 11 | { _ in a } 12 | } 13 | 14 | /// `I = SKK: A → A` 15 | func i(_ a: A) -> A { 16 | let k_: (A) -> (A) -> A = k 17 | let skk: (A) -> A = s(k)(k_) 18 | return skk(a) 19 | } 20 | 21 | i("hello") // "hello" 22 | i(123) // 123 23 | 24 | 25 | 26 | print("✅") 27 | -------------------------------------------------------------------------------- /SwiftAndLogic.playground/Sources/Types.swift: -------------------------------------------------------------------------------- 1 | /// A ∨ B 2 | public enum Either { 3 | case left(A) 4 | case right(B) 5 | } 6 | 7 | /// ¬A ≣ A → ⊥ 8 | /// (¬A ===> A → ⊥ , and A → ⊥ ===> ¬A) 9 | public struct Not { 10 | public let f: (A) -> Never 11 | 12 | public init(_ f: @escaping (A) -> Never) { 13 | self.f = f 14 | } 15 | } 16 | // or, `typealias Not = (A) -> Never` will also work. 17 | -------------------------------------------------------------------------------- /SwiftAndLogic.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------