wxs
wxs

Reputation: 5847

Implementing protocol on closure in Swift

I would like to create some functions in my Swift project that can accept either an object, or a closure which returns that object type. I could of course define the same function with multiple signatures in each place, but that's verbose. I also would like to be able to create type-safe lists of these object/object-returning-closures but can't do that without some common type describing both things.

This is what I would like to do

typealias StringClosure = () -> String

protocol Stringable {
    func toStringClosure() -> StringClosure
}

extension String : Stringable {
    func toStringClosure() -> StringClosure {
        return { return self }
    }
}

extension StringClosure : Stringable {
    func toStringClosure() -> StringClosure {
        return self
    }
}

func printStringable(a : Stringable) {
    print(a.toStringClosure()())
}


var stringableList : Stringable[] = ["cat", {return "dog"}, "gecko"]

for stringable in StringableList {
    printStringable(stringable)
}

But this does not work, because I can't actually extend my StringClosure type to implement Stringable. I could make stringableList a list of Any types, but that's not type safe.

The enum solution

One solution is that I could make an enum type, but that means I have to explicitly annotate everywhere I use these types with the enum, which is lame. That would look like this:

enum StringableEnum {
    case Str(String)
    case Fun(StringClosure)
}

func printStringableEnum(a : StringableEnum) {
    switch (a) {
    case let .Str(value):
        print(value)
    case let .Fun(value):
        print(value())
    }
}

var enumList : StringableEnum[] = [.Str("cat"), .Fun({return "dog"}), .Str("gecko")]

for element in enumList {
    printStringableEnum(element)
}

It's not bad, but it requires the user of my API to now know about this enum, and label their parameters with .Str or .Fun every time they call my printStringableEnum function. Not exactly a nice API!

This may be too much to ask of the language, but does anyone have any better ideas?

Upvotes: 4

Views: 2678

Answers (3)

Wes Campaigne
Wes Campaigne

Reputation: 4170

EDIT: Don't do the following. It works as of Beta 4, but relies on a feature that Apple doesn't want the public using and intends to remove before the 1.0 release of Swift.


@auto_closure might work for you, but the issue with that is that it seems you can't pass explicit closures to @auto_closure parameters.

Another alternative is to treat everything as closures, and use Swift's __conversion feature to implicitly convert strings to StringClosures where needed:

typealias StringClosure = () -> String

extension String {
    func __conversion() -> StringClosure {
        return { self } // 'return' can be omitted inside a single-expression closure
    }
}

// type inferencing automatically figures out that
// stringableList should be [StringClosure] and applies
// the conversion method to promote the string entries into
// self-returning closures
var stringableList = ["cat", { println("side-effects are bad"); return "dog"}, "gecko"]

func printStringClosure(s: StringClosure) {
    println(s())
}

for s in stringableList {
    printStringClosure(s)
}

printStringClosure("test")
printStringClosure { let a = 5*5; return "\(a)" }

Upvotes: 2

drewag
drewag

Reputation: 94753

Don't forget that swift has the option to create lazily calculated variables like so (they are not restricted to classes / structs):

var lazy : String {
    return "Hello"
}

printStringable(lazy)

This is a simpler solution to your lazy calculations / closures that have side effects problem if you don't need parameterized closures.

Upvotes: 0

drewag
drewag

Reputation: 94753

I think in this case, the simplest solution might be the best. Don't allow Strings to be passed directly in, instead require closures. It isn't very hard to turn a string into a closure:

let myString = "Hello"
printStringable({return  myString})

You could even create a function to turn a value into a closure for convenience:

func f<T>(value : T) -> () -> T  {
    return {return value}
}

printStringable(f("Hello"))
printStringable(f(myString))

Although I don't think the few characters saved is worth it the potentially confusing function name.

Edit:

You could also improve your enum like so:

enum StringableEnum {
    case Str(String)
    case Fun(() -> String)

    init(_ string : String) {
        self = .Str(string)
    }

    init(_ closure : () -> String) {
        self = .Fun(closure)
    }

    var value : String {
        switch(self) {
            case let .Str(value):
                return value
            case let .Fun(closure):
                return closure()
        }
    }
}

This means that you can create a enum from any of the supported types like so:

var stringable = StringableEnum("Hello")
stringable = StringableEnum({return "Hello"})

and you can get the string out by doing

stringable.value

Upvotes: 2

Related Questions