From this article 2 Minute Tips: The Dark Secret of Optionals I got to know that the Optionals are nothing but an Enum
. I started experimenting: How can we build Optional from scratch
Enter Schrödinger's Cat or … a box
You might have heard of Schrödinger’s Cat — the thought experiment where a cat in a box is both alive and dead until you check. That's basically an Optional
, right? A value that might exist... or not.
So, let's turn this idea into code:
enum 📦<Value> {
case 😺(Value)
case 😵
init(_ from: Value) {
self = .😺(from)
}
}
Now we can do:
let x: 📦 = .😺(123)
let y: 📦 = .😵
But that's not quite as convenient as Swift's native syntax: let x: Int? = 123
.
Can we make ours feel just as nice?
Literal support
Swift lets types conform to ExpressibleByNilLiteral
. Let’s do that:
extension 📦: ExpressibleByNilLiteral {
init(nilLiteral: ()) {
self = .😵
}
}
Now we can say:
let y = nil as 📦<Int>
Note: we still need to add as since nil as no type context.
Nice! But we can't do let x: 📦
directly — Swift won't let us. As this StackOverflow post explains, that’s a compiler-level limitation.
But hey, we are creative. Let’s define a custom postfix operator to return the optional:
postfix operator ❓
postfix func ❓<T>(value: T) -> 📦<T> {
.😺(value)
}
Now the usage becomes:
let x = 123❓ // same as .😺(123)
let y = nil as 📦<Int> // same as .😵
Lets implement force unwrapping
Again we can use a custom postfix operator for this
postfix operator ❗
postfix func ❗<T>(value: 📦<T>) -> T {
switch value {
case .😺(let v):
return v
case .😵:
fatalError("Cat is dead Bruh!")
}
}
Nil Coalescing
Lets add ??
-like operator
infix operator ❓➡️: NilCoalescingPrecedence
func ❓➡️<T>(lhs: 📦<T>, rhs: @autoclosure () -> T) -> T {
switch lhs {
case .😺(let value):
return value
case .😵:
return rhs()
}
}
let value1 = x ❓➡️ 999 // 123
let value2 = y ❓➡️ 999 // 999
Optional Chaining
This was the complex part that I had to workout. For this I created a wrapper Optional type which has a @dynamicMemberLookup
on it.
@dynamicMemberLookup
struct _📦<Value> {
private let storage: 📦<Value>
init(from: 📦<Value>) {
storage = from
}
subscript<T>(dynamicMember keyPath: KeyPath<Value, 📦<T>>) -> 📦<T> {
switch storage {
case .😺(let v):
return v[keyPath: keyPath]
case .😵:
return .😵
}
}
}
postfix operator ❔
postfix func ❔<T>(value: 📦<T>) -> _📦<T> {
_📦(from: value)
}
let try it
struct TheInteger {
let x: 📦<Int>
}
struct Service {
let integer: 📦<TheInteger>
}
let service = Service(integer: TheInteger(x: 27❓)❓)
let s = service.integer❔.x // 😺(27)
print(s)
Conclusion
Why bother? — Because its fun and I got to play with under the hood stuff that I will probably not use in a production environment
Here is the complete code: https://gist.github.com/rational-kunal/96ddaf8f0da2cd03f4edac190f2263ce
Also here are the list of referneces:
- https://blog.jacobstechtavern.com/p/2-minute-tips-the-dark-secret-of?utm_source=publication-search
- https://www.swiftbysundell.com/articles/custom-operators-in-swift/
- https://swiftwithmajid.com/2023/05/23/dynamic-member-lookup-in-swift
Thank you!