1

I'm an experience programmer, but I just started learning Swift (version 4) recently.

I found it difficult to do simple tasks, even like "give me the 4th Character of this String".

I tried to write these two simple functions:

// isHexCharacter returns whether a Character is hexadecimal or not
func isHexCharacter(_ c: Character) -> Bool {
    switch c {
    case "0", "1", "2", "3", "4", "5", "6", "7",
            "8", "9", "A", "B", "C", "D", "E", "F":
        return true
    default:
        return false
    }
}

// isHexString returns whether a String (or Substring) consists
// entirely of hexadecimal characters
func isHexString(_ s: StringProtocol) -> Bool {
    for c in s {
        if !isHexCharacter(c) {
            return false
        }
    }
    return true
}

But the compiler bit me:

demo.swift:20:23: error: protocol 'StringProtocol' can only be used
    as a generic constraint because it has Self or associated
    type requirements
func isHexString(_ s: StringProtocol) -> Bool {
                      ^
demo.swift:21:14: error: using 'StringProtocol' as a concrete type
conforming to protocol 'Sequence' is not supported
    for c in s {
             ^

My questions are:

  • I don't understand the compiler messages. What do they mean? Why couldn't I use StringProtocol as a parameter? Did I miss something?

  • I know that there may be standard library functions that exactly fit my job above, but it's just a demo. My point is knowing how to write more complicated functions that work on either String or Substring. How could I do that?

Thank you so much!

UPDATED: 2017-09-28 08:05 UTC

As suggested by @martin-r, now I changed isHexString like this:

func isHexString<S: StringProtocol>(_ s: S) -> Bool {
    for c in s {
        if !isHexCharacter(c) {
            return false
        }
    }
    return true
}

It works perfectly!

However, I tried to create the following code:

protocol Animal {
    func eat()
}

class Cat : Animal {
    func eat() {
        print("Meow")
    }
}

func animalEat(_ a: Animal) {
    a.eat()
}

var kitty = Cat()
animalEat(kitty)

I have no idea why this works without error. Why does the function animalEat work fine without generics?

  • Thanks! Your solution works fine! My question is updated. As I mentioned, instead of just solving the problem shown, my target is to understand why and so I know how to deal with it next time. Thanks! – Siu Ching Pong -Asuka Kenji- Sep 28 '17 at 08:12
  • 1
    It makes a difference if the protocol has associated types or not. You'll find a detailed explanation in the answer to the questoin that I linked to. – Martin R Sep 28 '17 at 08:18

1 Answers1

2

I don't understand the compiler messages. What do they mean?

In Swift, you can have a generic protocol by giving it an "associated type". For example

protocol Stack
{
    associated type Element

    func push(e: Element)
    func pop() -> Element
}

The above is like having a generic interface in Java interface Stack<T> ....

However, in Swift, as soon as you put an associated type in a protocol, you can no longer treat it like an ordinary type and you have to start jumping through hoops. So you can't do

func myFunc(x: Stack<Int>)

you have to make it generic over types that conform to the protocol

func myFunc<T: Stack>(x: T) where T.Element == Int // Not sure if the syntax is exactly right here

which is kind of saying the same thing but looks more convoluted. I don't know why it is like that, it's either an implementation issue or something to do with type safety and type inference.

The problem is that StringProtocol has three associated types: UTF8View, UTF16View and UnicodeScalarView. These can be anything the conforming type likes, as long as they are collections of UInt8, UInt16 or UnicodeScalar respectively.

My point is knowing how to write more complicated functions that work on either String or Substring. How could I do that?

Just looked up the Int initialiser that initialises from a String and the signature from that is similar to what you need i.e.

convenience init?<S>(_ text: S, radix: Int = default) where S : StringProtocol

If your ultimate aim is to get a hex number, I'd use this directly.

guard let x = Int("0123F", radix: 16) else { /* Not a hex string */ }
JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Thank you so much! I'm clearer now! I think Swift's documentation is not clear compared with other languages. For example, in a type's documentation, the return types of the methods are not shown. The developer needs to click into the link to see the result type. This makes it difficult to use! For example, if I want to find all methods of `String` that return `Character`, I need to click into each page. For other languages, such as Java and Go, I only need to search through the page with the browser. – Siu Ching Pong -Asuka Kenji- Sep 28 '17 at 14:02
  • @SiuChingPong-AsukaKenji- Sometimes it's easier just to look at the definition. If you are using Xcode, you can right click on a type name in the source code and do "jump to definition". – JeremyP Sep 28 '17 at 14:09
  • The Swift syntax you shown above is also difficult to master. What's the reason behind it? Besides, there's no clue in the doc that it's related to generics. For example, the [doc of `Int`](https://developer.apple.com/documentation/swift/int) shows that it is a **Structure**, while [that of `Array`](https://developer.apple.com/documentation/swift/array) shows that it's a **Generic Structure**. But [that of `StringProtocol`](https://developer.apple.com/documentation/swift/stringprotocol) only shows **Protocol**, but not **Generic Protocol**. This adds unnecessary learning curve for new comers. – Siu Ching Pong -Asuka Kenji- Sep 28 '17 at 14:14
  • I understand that using Xcode is better, but I'm not a big fan of Xcode. I'm a fan of editors but no IDEs. Apple can definitely show the return types in the documentation. I have no idea why they made the choice of not showing them! – Siu Ching Pong -Asuka Kenji- Sep 28 '17 at 14:18
  • 1
    @SiuChingPong-AsukaKenji- All true and good criticisms. I think an overhaul of the Swift generic system was on the road map and maybe it'll get done in Swift 5. If you want a say, you can join the Swift Evolution mailing list. – JeremyP Sep 28 '17 at 14:18