4
\$\begingroup\$

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.

  1. There is quite a bit of code duplication due to a few nearly identical functions being needed.
  2. The closure sent to the caze functions are escaping, though they are intended to never actually escape beyond a very defined scope.
  3. If code in a caze function throws, it doesn't get rethrown.
  4. 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?

\$\endgroup\$
  • \$\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 Jun 7 '16 at 9:11
1
\$\begingroup\$
  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 Tconforms 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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.