Skip to content

Instantly share code, notes, and snippets.

@TizianoCoroneo
Last active June 7, 2019 19:32
Show Gist options
  • Save TizianoCoroneo/76b51699240dc2579082bc29662e0676 to your computer and use it in GitHub Desktop.
Save TizianoCoroneo/76b51699240dc2579082bc29662e0676 to your computer and use it in GitHub Desktop.
Even or Odd property delegate playground in Swift 5.1
import Foundation
/*:
The goal is to have some kind of "even box" type that can contain only even numbers, and a "odd box" that can contain only odd numbers.
With that setup, it is possible to define some operations between "boxes" to check at compile time if the result of an expression will be even or odd.
*/
/**
A protocol in common for `Even<T>` and `Odd<T>`.
Every Even or Odd box should have its `value`, and a `name`: `"even"` or `"odd"`.
They also have an init with a precondition check in case we try to "lie" about what number we want to put in the box:
```
@Even var x = 3 // preconditionFailure()
```
*/
protocol EvenOrOdd: CustomStringConvertible {
// Underlying number type. May be Int16, Int32, Int64, Int...
associatedtype T: FixedWidthInteger
// The type reference to the "Other" type of box.
// It should be `Odd<T>` if `Self == Even<T>`, should be `Even<T>` if `Self == Odd<T>`.
associatedtype Other: EvenOrOdd where Other.Other == Self, Other.T == T
/// `"even"` or `"odd"`?
var name: String { get }
/// Numeric value inside the box.
var value: T { get set }
/**
An init that fails in case we try to "lie" about what number we want to put in the box:
```
@Even var x = 3 // preconditionFailure()
```
*/
init(initialValue: T)
}
extension EvenOrOdd {
init(_ value: T) {
self.init(initialValue: value)
}
/// Naming is hard but descripting is harder
var description: String {
return "\(value) is \(name)"
}
}
/*:
Now let's start with actually defining the boxes!
Here's the `Even` box.
*/
@propertyDelegate struct Even<T: FixedWidthInteger>: EvenOrOdd {
typealias Other = Odd<T>
/// `"even"`!
var name: String { return "even" }
private var number: T
var value: T {
get { number }
set {
precondition(
newValue % 2 == 1,
"Value \(newValue) is not \(name)")
number = newValue
}
}
init(initialValue: T) {
number = initialValue
precondition(
initialValue % 2 == 0,
"Value \(initialValue) is not \(name)")
}
}
//: And the Odd box.
@propertyDelegate struct Odd<T: FixedWidthInteger>: EvenOrOdd {
typealias Other = Even<T>
/// `"odd"`!
var name: String { return "odd" }
private var number: T
var value: T {
get { number }
set {
precondition(
newValue % 2 == 1,
"Value \(newValue) is not \(name)")
number = newValue
}
}
init(initialValue: T) {
number = initialValue
precondition(
initialValue % 2 == 1,
"Value \(initialValue) is not \(name)")
}
}
/*:
Let's test if everything goes as expected.
If you don't lie, you get a box.
If you lie, you get an exception.
*/
struct Test {
@Odd var _1 = 1
@Even var _2 = 2
@Odd var _3 = 3
@Even var _4 = 4
@Odd var _5 = 5
@Even var _6 = 6
/// If you remove the comment you can see the preconditionFailure.
// @Even var boom = 5
/// This function triggers the precondition as well.
mutating func makeMyselfGoBOOM() {
self._1 = 2
}
}
var test = Test()
test.makeMyselfGoBOOM()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment