Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
438 views
in Technique[技术] by (71.8m points)

How to determine if a generic is an optional in Swift?

I want to extend an Array with a function that would return a count of all non-nil items in an Array. Ideally this would work with an array of any optional or non-optional types. I tried a variety of things that failed to compile, crashed Xcode or both. I would have assumed it would look something like this:

extension Array {
    func realCount() -> Int {
        var cnt = 0
        for value in self {
            if value != nil {
                cnt++
            }
        }

        return cnt
    }
}

Here Swift complains that T is not convertible to UInt8. Or sometimes MirrorDisposition or other random classes.

So assuming it's possible, what's the trick?

Edit: as of Xcode 6 beta 5 this now compiles but does not give the expected results. if value != nil evaluates true every time.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can't compare an arbitrary value to nil (EDIT: but see Sulthan's comment below; it may be that we should be able to compare arbitrary values to nil; the rest of this paragraph may be true today, but only due to a compiler bug). While Optional has some bits of syntactic sugar applied to it, it's really just a enum, and nil is just Optional.None. You want one behavior for one type (Optional) and another behavior for all other types. Swift has that via generics, just not in extensions. You have to turn it around into a function:

func realCount<T>(x: [T?]) -> Int {
  return countElements(filter(x, { $0.getLogicValue() } ) )
}

func realCount<T>(x: [T]) -> Int {
  return countElements(x)
}

let l = [1,2,3]
let lop:[Int?] = [1, nil, 2]

let countL = realCount(l) // 3
let countLop = realCount(lop) // 2

This approach is much more flexible. Optional is just one of many types you would want to flatMap this way (for example, you could use this same technique to handle Result).


EDIT: You can take this further by creating a protocol for things you consider "real." That way you don't have to confine this to Optionals. For example:

protocol Realizable {
  func isReal() -> Bool
}

extension Optional: Realizable {
  func isReal() -> Bool { return self.getLogicValue() }
}

func countReal<S:Collection>(x: S) -> S.IndexType.DistanceType {
  return countElements(x)
}

func countReal<S:Collection where S.GeneratorType.Element:Realizable>(x: S) -> Int {
  return countElements(filter(x, {$0.isReal()}))
}

This says, if I pass a collection of "realizable" things, then filter them against their rule. Otherwise, just count them. While I probably wouldn't really use this function (it seems very special-case), the concept is useful. Later callers can add new "realizable" types without modifying any of your code (or even knowing how they're implemented). And this shows how to have a default behavior for things that don't implement your protocol.

BTW, I'm using Collections here just because they're easier to count (and I'm being a bit sloppy about the return types; notice one is the DistanceType and the other is an Int). Getting the types right on generic Collection-based functions is still kind of tricky (and often crashes the compiler). I suspect this will all improve in the next betas.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

56.8k users

...