I tried to use pattern matching. I have code like this:
struct Quantity {
let quantity: Int
let unit: Unit?
init(_ quantity: Int, unit: Unit? = nil) {
self.quantity = quantity
self.unit = unit
}
}
enum Unit {
case liter
case kilogram
case meter
}
and I wanted to write a function that formats such a quantity nicely. I started writing it like this:
func formatQuantity(_ quantity: Quantity) -> String { }
I then wanted to switch on quantity. I tried the following syntax:
case Quantity(let quantity, unit: nil)
case let Quantity(quantity, unit: nil)
case Quantity(quantity: let quantity, unit: nil)
…but none of them work. Is this possible to do somehow?
It seems to interpret Quantity(quantity, unit: nil) as an expression, even with let before it, and let quantity inside an expression as not allowed.
You can do it with ~=. Search for something like "Swift pattern matching ~=".
But, it doesn't make sense for Quantity. The reason you switch on an enum is to access its associated values and/or handle multiple cases. The only thing you need to switch on here is unit.
2 Likes
trochoid
(Trochoid)
4
Maybe what you want is tuple matching
func formatQuantity(_ quantity: Quantity) -> String {
switch (quantity.quantity, quantity.unit) {
case (let q, nil): "\(q) nil"
case (let q, .liter): "\(q) liter"
default: "unmatched"
}
}
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/controlflow#Tuples
1 Like
beccadax
(Becca Royal-Gordon)
5
There’s nothing built in for it, because there’s no way to “reverse” a struct initializer. But you can always have a computed property return a tuple:
extension Quantity {
var destructured: (quantity: Int, unit: Unit?) { (quantity, unit) }
}
switch quantity.destructured {
case (let quantity, nil):
…
default:
…
}
3 Likes
AlexanderM
(Alexander Momchilov)
6
Seems reasonable to me, given that structs are more than just transparent bags of values (like e.g. non-opaque structs in C). That's what tuples are.
1 Like
eskimo
(Quinn “The Eskimo!”)
7
You can even do this generically:
protocol Destructurable {
func destructured<each Property>(_ key: repeat (KeyPath<Self, each Property>)) -> (repeat each Property)
}
extension Destructurable {
func destructured<each Property>(_ key: repeat (KeyPath<Self, each Property>)) -> (repeat each Property) {
return (repeat self[keyPath: each key])
}
}
Allowing for this:
struct Quantity {
var quantity: Int
var unit: String
}
extension Quantity: Destructurable { }
func main() {
let q = Quantity(quantity: 42, unit: "bablefish")
switch q.destructured(\.quantity, \.unit) {
case (42, let unit):
print(unit)
default:
print("default")
}
// prints: bablefish
}
main()
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
18 Likes
ole
(Ole Begemann)
8
I love this, thanks for sharing! And it even works without this, i.e. fully generically:
3 Likes
eskimo
(Quinn “The Eskimo!”)
9
thanks for sharing!
You’re welcome.
And it even works without this
D’oh! Yeah, that was left over from my evolution from Becca’s example. I edited my post to remove it.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
2 Likes
Key paths don't support throwing properties
, and rethrows doesn't work with parameter packs, but you can support both
q.apply(\.quantity, \.unit)
and
extension Quantity {
enum Error: Swift.Error { }
var property: Bool { get throws(Error) { .init() } }
}
try q.apply(
{ $0.quantity },
{ $0.unit },
{ try $0.property }
)
with
extension Destructurable {
func apply<each Transformed, Error: Swift.Error>(
_ transform: repeat (Self) throws(Error) -> each Transformed
) throws(Error) -> (repeat each Transformed) {
try (repeat (each transform)(self))
}
}
(This probably makes more sense as a free function because it supports more than destructuring.)
1 Like
This code doesn’t see
This code doesn’t seem to compile on my installation, even if I move it to a standalone function…
bzamayo
(Benjamin Mayo)
14
This is a cool trick, but practically when is this better than switch (q.quantity, q.unit) {? You have to write out the property names in full in both cases, and obviously the pack approach has a lot more associated boilerplate (and even the call site is longer character-count wise).
7 Likes
eskimo
(Quinn “The Eskimo!”)
15
and even the call site is longer character-count wise
Sure, if your variable name is q. With a realistic variable name, like latestQuantity, the difference starts to start.
However, I tend to agree with your larger point. I doubt I’ll adopt this technique in my own code. I was mostly just using this as an excuse to work on my variadic generic skills (-:
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
3 Likes