I made a bunch of functions and an enumeration, that together allow me to write something like a switch expression. It's not as powerful as Swift switch statements, since it's lacking pattern matching and I don't intend to use this in real code, it was just an experiment. Here is what I got:
enum Caze<T: ForwardIndexType where T: Equatable> {
case RangeCaze(Range<T>)
case ArrayCaze([T])
func contains(elem: T) -> Bool {
switch self {
case let .RangeCaze(r):
return r.contains(elem)
case let .ArrayCaze(arr):
return arr.contains(elem)
}
}
}
func caze<T: ForwardIndexType, R where T: Equatable>(vals: T..., @autoclosure(escaping) ret f: () -> R) -> (Caze<T>, () -> R) {
return (.ArrayCaze(vals), f)
}
func caze<T: ForwardIndexType, R where T: Equatable>(vals: Range<T>, @autoclosure(escaping) ret f: () -> R) -> (Caze<T>, () -> R) {
return (.RangeCaze(vals), f)
}
func caze<T: Equatable, R>(vals: T..., @autoclosure(escaping) ret f: () throws -> R) -> ([T], () throws -> R) {
return (vals, f)
}
func schwitch<T: ForwardIndexType, R where T: Equatable>(value: T, _ cases: (Caze<T>, () -> R)..., @autoclosure def: () throws -> R) rethrows -> R {
for (vals, f) in cases {
if vals.contains(value) {
return f()
}
}
return try def()
}
func schwitch<T: Equatable, R>(value: T, _ cases: ([T], () -> R)..., @autoclosure def: () throws -> R) rethrows -> R {
for (vals, f) in cases {
if vals.contains(value) {
return f()
}
}
return try def()
}
This allows me to write code like:
schwitch(5, // results in "lol"
caze(0, ret: "hello"),
caze(1, 2, ret: "test"),
caze(3..<7, ret: "lol"),
def: "nop")
schwitch("helloo", //results in "nop"
caze("hello", "Hello", ret: "test"),
def: "nop")
I think there are a few problems with this though.
- There is quite a bit of code duplication due to a few nearly identical functions being needed.
- The closure sent to the
caze
functions are escaping, though they are intended to never actually escape beyond a very defined scope. - If code in a
caze
function throws, it doesn't get rethrown. I also considered an alternative implementation for the
schwitch
functions but am worried that it takes brevity a bit too far:(cases.filter{0ドル.contains(value)}.first ?? def)()
Are there any ways to improve on these 4 points and are there any other things that I missed or could have done better?
-
\$\begingroup\$ I have started an effort to improve my own code but have hit a road block since Xcode hangs up on my code. I have posted a question on SO about it. \$\endgroup\$overactor– overactor2016年06月07日 09:11:58 +00:00Commented Jun 7, 2016 at 9:11
1 Answer 1
- There is quite a bit of code duplication due to a few nearly identical functions being needed.
This is probably the biggest issue and the one I'll be addressing here.
The two schwitch
functions have identical bodies, this is a major code smell. The only difference between the two is the generic constraints on T
and the type T
is wrapped up in (Array
and Caze
repectively). You don't actually care that T
conforms to ForwardIndexType
in the schwitch
function and the only way I was using both Caze
and Array
was by calling contains
on them (with identical signatures). This clearly calls for a protocol. And I didn't even need to declare one myself. the contains
function I was using is declared in SequenceType
, a protocol both Array
and Range
conform to. So here's what the new (and only) schwitch
function will look like:
func schwitch<S: SequenceType, R where S.Generator.Element: Equatable>(value: S.Generator.Element, _ cases: (S, () -> R)..., @autoclosure def: () -> R) -> R {
for (vals, f) in cases {
if vals.contains(value) {
return f()
}
}
return def()
}
You will notice that function is now missing a rethrows
, the swift compiler hangs up on that due to a bug.
This also allowed me to get rid of the Caze
enum and only have two caze
functions, one that takes any amount of values and one that takes a range:
func caze<T: Equatable, R>(vals: T..., @autoclosure(escaping) ret f: () -> R) -> (AnySequence<T>, () -> R) {
return (AnySequence(vals), f)
}
func caze<T, R where T: Equatable, T: ForwardIndexType>(range: Range<T>, @autoclosure(escaping) ret f: () -> R) -> (AnySequence<T>, () -> R) {
return (AnySequence(range), f)
}
I've made both functions return an AnySequence<T>
instead of more concrete type, I think that makes sense, considering how the results are used, but it's really up to you if you maybe want to return Array<T>
and Range<T>
instead.
I can still use my function exactly as I did before.