44

Consider this myFilter function that takes in a generic argument and filters the array based on the predicate. This is same as the filter() function provided by Swift.

func myFilter<T>(source: [T], predicate:(T) -> Bool) -> [T] {
  var result = [T]()
  for i in source {
    if predicate(i) {
      result.append(i)
    }
  }
  return result
}

How is this different from,

func myFilter(source: [AnyObject], predicate:(AnyObject) -> Bool) -> [AnyObject] {
  var result = [AnyObject]()
  for i in source {
    if predicate(i) {
      result.append(i)
    }
  }
  return result
}

Aren't we achieving the point of generics even in the latter example?

avismara
  • 5,141
  • 2
  • 32
  • 56
  • In regards to bounty: and perhaps something which includes this isn't doable with `generics` or this isn't doable with `AnyObject` – mfaani Jan 17 '17 at 16:40
  • @Honey Not entirely sure exactly what you're asking for in your bounty, but is [this Q&A](http://stackoverflow.com/q/38446487/2976878) helpful? – Hamish Jan 18 '17 at 14:24
  • @Hamish in terms of protocol oriented programming, where exactly AnyObject can't be used or generics can't be used. Icaro's answer is good, avismara's answer only adds more code to it, but doesn't go deeper. By deep I meant more Swifty, more protocol oriented. But obviously wasn't clear on the definition of deep :/ – mfaani Jan 18 '17 at 14:26
  • 1
    You would have to ask a different question for that, @Honey. Answering about POP here would be incorrect because that's not what's asked here. – avismara Jan 18 '17 at 14:28
  • @avismara I did mention POP specifically in the bounty. But maybe idk. I'll ask on meta if I should open another question or not. – mfaani Jan 18 '17 at 14:40
  • @Hamish that Q&A was a bit too dense for me :( – mfaani Jan 18 '17 at 15:49
  • 1
    Huh? You SHOULD open a new question for this because, while the concepts of generics is used in POP, it has got nothing to do with the question asked here -- which is about the difference between dynamic typing and static typing. Icaro's answer answers the question and mine just extends it, with the assumption that just the short explanation wasn't enough. This question has got nothing to do with POP *per se* and you shouldn't be expecting answers on it. Ask a new question. PS: You didn't "specifically" ask about POP. You just informed that you are learning it. – avismara Jan 18 '17 at 18:45
  • 2
    @avismara Your PS is exactly why I'm confused by this comment thread. Since the POP comment seems to be tangential, how does the bounty text change the nature of the question? Also, note that on meta, Honey wrote, "Swift isn't really OOP, it's POP". So I expect the "I'm in the learning phase..." sentence was intended to confess a sort of general ignorance of Swift *in general*, with the *assumption* that this question is related to POP in some way. – Kyle Strand Jan 18 '17 at 23:24
  • 2
    This question (and specifically the bounty) is being discussed [on meta](http://meta.stackoverflow.com/q/341813/1858225). – Kyle Strand Jan 18 '17 at 23:25

3 Answers3

89

Generics are type safe, meaning if you pass a string as a generic and try to use as a integer the compiler will complain and you will not be able to compile your (which is good). (This happens because Swift is using Static typing, and is able to give you a compiler error)

If you use AnyObject the compiler has no idea if the object can be treated as a String or as an Integer. It will allow you to do whatever you want with it (which is bad).

e.g. if you try to pass a String when it the your previously used Integer the application will crash. (This happens because Swift is using Dynamic typing and will only give you a runtime crash)

Generics basically tells the compiler:

"I am going to give you a type later and I want you to enforce that type everywhere I specify."

AnyObject basically tells the compiler:

"Don't worry about this variable no need to enforce any type here let me do whatever I want to."

mfaani
  • 33,269
  • 19
  • 164
  • 293
Icaro
  • 14,585
  • 6
  • 60
  • 75
  • How is it a generic, but then it enforces a type? Doesn't make sense – mfaani May 31 '16 at 14:33
  • 1
    @honey it enforces it to stick to its *previous* mentioned type! – mfaani Oct 04 '16 at 15:21
  • *It will allow you to do whatever you want with it (which is bad).* <-- Actually The compiler **will complain** if you want to use String funcs on something of type `AnyObject`. It will enforce you to downcast it – mfaani Jan 17 '17 at 16:10
  • 2
    @Honey You can send any known `@objc` message to something typed as `AnyObject` (it behaves much like Obj-C's `id`) – see the "[Dynamic Method Lookup](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-ID42)" section of the Swift with Cocoa and Objective-C guide. Although note that this answer is very old, and was written even before Swift 2 was officially released. – Hamish Jan 18 '17 at 14:19
  • Icaro, while your answer is indeed correct, I think the emphasis is slightly off. In this case, the emphasis should be on the ability to to enforce the *same* type everywhere, so in your "tells the compiler" you need to emphasize "everywhere" even stronger than the rest. –  Jan 20 '17 at 14:03
14

Note: Icaro's answer would still be the accepted answer, I am just extending his explanation.

TL;DR : Check Icaro's answer.

About the usage of AnyObject Icaro rightly puts:

Don't worry about this variable, no need to enforce any type here let me do whatever I want to.

What does this mean? Let's take the code example in the question (I've gone a step up and changed AnyObject to Any without changing the meaning of the question):

func myFilter(source: [Any], predicate:(Any) -> Bool) -> [Any] {
  var result = [Any]()
  for i in source {
    if predicate(i) {
      result.append(i)
    }
  }
  return result
}

This means, the myFilter function takes in two arguments one source and a closure predicate. If you look closely, the contents of the source array can be ANYTHING. And the argument of the closure predicate can be ANYTHING as well. If we were to name these "ANYTHING"s -- say ANYTHING1 and ANYTHING2 -- this approach doesn't require ANYTHING1 to be equal to ANYTHING2.

Let's sit back and ponder over the implications of this...

Say, we want to filter out evens from an array of integers and let's use our Any filter for this

var ints = [1,2,3,4,5] as [Any]
var predicate = { (a : Any) -> Bool in
    return (a as! Int) % 2 == 0
}

let evens = myFilter(source: ints, predicate:predicate)

Wow, that worked, didn't it? All smiles? No.

Notice how in the line :

return (a as! Int) % 2 == 0

I'm forcefully down-casting a. This line would crash if a was anything other than an Int. But its usage is justified; after all, we want to just filter out the Ints and I am smart enough to use just an array of Ints.

But, because say, I am a naive programmer, I do this :

var ints = [1,2,3,4,5,"6"] as [Any]
var predicate = { (a : Any) -> Bool in
    return (a as! Int) % 2 == 0
}

let evens = myFilter(source: ints, predicate:predicate)

This happily compiles, but crashes in the runtime. If only there was a way, where the compiler would tell me that this line...

var ints = [1,2,3,4,5,"6"]

... was faulty, we would not have had a crash. I would have fixed it right away!

Turns out, there is. Generics. To quote Icaro again,

I am going to give you a type later and I want you to enforce that type everywhere I specify.

func myFilter<T>(source: [T], predicate:(T) -> Bool) -> [T] {
    var result = [T]()
    for i in source {
        if predicate(i) {
            result.append(i)
        }
    }
    return result
}

var ints = [1,2,3,4,5,6]
var predicate = { (a : Int) -> Bool in
    return a % 2 == 0
}

let evens = myFilter(source: ints, predicate:predicate)

This new filter is awesome. It won't let me do :

let evens = myFilter(source: ints, predicate:predicate) because, the types of the predicate and source don't match. Compile time error.

Generics is generic in this way : in this specific example -- while at the point of writing the myFilter function, you do not need to give a type of the source or the argument that predicate takes, it's T, it's ANYTHING. But once I say that source is an array of ANYTHING, you HAVE to make sure the argument that the predicate accepts is the same ANYTHING. With the background of our previous ANYTHING1, ANYTHING2 nomenclature, we can say that generics forces ANYTHING1 to be equal to ANYTHING2

Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
avismara
  • 5,141
  • 2
  • 32
  • 56
  • So I see 2 benefits one 1. same type of T, as in we won't have Int vs. String 2. No need to downcast. Generics would handle the casting? <-- can you elaborate about the 2nd point? – mfaani Jan 17 '17 at 19:30
  • Generics doesn't handle casting. But you answered your question yourself. There is no need to cast because of the first benefit. I know it is of some type `T`. And the compiler has forced me to use it. – avismara Jan 17 '17 at 19:33
1

Consider that in the first function T is not a type, like is AnyObject, but a type variable; this means that in the first function you can pass an array of values of any type as first parameter, but a predicate which operates only on values of that specific type as second parameter. That is you can pass an array of strings and a predicate on strings, or an array of integers and a predicate on integers, while you cannot pass an array of integers and a predicate on strings. So, the body of the function is guaranteed to be correct for what concern the types.

Instead in the second example you can pass a value of any type and a predicate which operates on any (possibly different!) type, so that, if the predicate would be called in the body of the function with the value of the first parameter, then a dynamic type error could occur. Fortunately, the Swith typechecker marks the call of the predicate as type error, in order to prevent this possibility.

Renzo
  • 26,848
  • 5
  • 49
  • 61