Last active
May 29, 2024 10:22
-
-
Save TizianoCoroneo/ae263bef4d5ba4767669ba161c67c5e4 to your computer and use it in GitHub Desktop.
Type level FizzBuzz in Swift 5.10
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func namespace(_ f: () -> ()) -> () { f() } | |
// A fizz buzz implementation in Swift | |
namespace { | |
let range = 1...100 | |
let result = range.map { (n: Int) -> String in | |
switch (n % 3, n % 5) { | |
case (0, 0): "Fizz Buzz" | |
case (0, _): "Fizz" | |
case (_, 0): "Buzz" | |
default: "\(n)" | |
} | |
}.joined(separator: "\n") | |
print(result) | |
} | |
// At a type level now! | |
// First, typelevel Peano construction of natural numbers. | |
protocol NumType { | |
associatedtype Previous: NumType | |
static var value: Int { get } | |
} | |
// For every natural number, its predecessor is also a natural number. | |
// The predecessor of 0 is `Never`, and the predecessor of `Never` is `Never`, | |
// tying up the `Previous` associatedtype recursion. | |
extension Never: NumType { | |
typealias Previous = Never | |
static var value: Int { fatalError() } | |
} | |
// Define 0 and the successor of a natural number. | |
// This way I get all the numbers by induction. | |
enum NZero: NumType { | |
typealias Previous = Never | |
static var value: Int { return 0 } } | |
enum NSucc<N: NumType>: NumType { | |
typealias Previous = N | |
static var value: Int { return N.value + 1 } } | |
typealias N1 = NSucc<NZero> | |
typealias N2 = NSucc<N1> | |
typealias N3 = NSucc<N2> | |
typealias N4 = NSucc<N3> | |
typealias N5 = NSucc<N4> | |
typealias N6 = NSucc<N5> | |
typealias N7 = NSucc<N6> | |
typealias N8 = NSucc<N7> | |
typealias N9 = NSucc<N8> | |
typealias N10 = NSucc<N9> | |
typealias N11 = NSucc<N10> | |
typealias N12 = NSucc<N11> | |
typealias N13 = NSucc<N12> | |
typealias N14 = NSucc<N13> | |
typealias N15 = NSucc<N14> | |
// Now Fizz Buzz! | |
protocol Fizz {} | |
protocol Buzz {} | |
typealias FizzBuzz = Fizz & Buzz | |
// This is an unfortunate side effect of this implementation: | |
// I have to make the 0 both fizz and buzz to tie the associatedtype recursion up. | |
extension NZero: Fizz {} | |
extension NZero: Buzz {} | |
// Then I can define: | |
// a number is Fizz if the number - 3 is also Fizz | |
// a number is Buzz if the number - 5 is also Buzz | |
extension NSucc: Fizz where Previous.Previous.Previous: Fizz {} | |
extension NSucc: Buzz where Previous.Previous.Previous.Previous.Previous: Buzz {} | |
// Some utilities to check if a type number is Fizz, Buzz or FizzBuzz | |
// Should be probably easier to check in Swift 5.1, using the new `some` keyword for opaque result types. | |
func testFizz<N: NumType>(_ n: N.Type) where N: Fizz {} | |
func testBuzz<N: NumType>(_ n: N.Type) where N: Buzz {} | |
func testFizzBuzz<N: NumType>(_ n: N.Type) where N: FizzBuzz {} | |
// The commented lines result in compile-time errors. | |
testFizz(NZero.self) | |
//testFizz(N1.self) | |
//testFizz(N2.self) | |
testFizz(N3.self) | |
//testFizz(N4.self) | |
testBuzz(N5.self) | |
testFizz(N6.self) | |
//testFizz(N7.self) | |
//testFizz(N8.self) | |
testFizz(N9.self) | |
testBuzz(N10.self) | |
//testBuzz(N11.self) | |
testFizz(N12.self) | |
//testFizz(N13.self) | |
//testFizz(N14.self) | |
testFizzBuzz(N15.self) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment