6 ms·
Limitations of protocols in Swift
- Manishearth 11y agoI'm not a Swift person, so I might be wrong, but this seems like a case where composition over inheritance is preferred. Protocols in Swift (and traits in Rust) are not supposed to be used to emulate classical inheritance; though they can be. Enums/ADTs/sum types/whatever you call them are what you're supposed to use in such cases.
- alexjarvis 11y agoYeah you're right. If you want to stick with value types and store those values in an Array where the array can only store one type then you have to use an Enum instead of classes. I think there are a few people getting confused by not identifying enums in Swift as a more powerful construct than in C/ObjC.
- Manishearth 11y agoWe basically have that same issue in Rust :)
- alexjarvis 11y agoAnd with associated values in enum types they behave in a similar way to case classes in Scala that extend a sealed trait. For example I've been working on a Json library that stores everything in an enum (ADT): public enum JsValue { case JsString(String) case JsNumber(Double) case JsObject([String: JsValue]) case JsArray([JsValue]) case JsBoolean(Bool) case JsNull }
- kpmah 11y agoDon't trait objects (https://doc.rust-lang.org/stable/reference.html#trait-objects https://doc.rust-lang.org/stable/reference.html#trait-object...) solve this problem?
- Manishearth 11y agoWell, yes. But that wasn't what I was talking about. Trait objects in Rust let you emulate inheritance. The issue I was talking about is that folks from inheritance-heavy languages use this as the de facto design pattern, and then stuff doesn't work so well, because you're not supposed to use inheritance so much (the languages aren't designed with this in mind). The correct option is to use enums and composition over inheritance wherever possible.
- seanmcdirmid 11y agoIn functional languages, functionality should be extrinsic (case match values from the outside), while in OO languages like functionality is intrinsic (polymorphic method members called on the value). However, for things like hashing, intrinsic works way better (hence traits, protocols, type classes, and so on). Saying use composition over inheritance "wherever possible" can lead to some cargo culting on its own. It is always possible to use composition over inheritance, but it isn't always wise.
- Manishearth 11y ago> but it isn't always wise. Pretty much what I meant, when I said "wherever possible" I meant "wherever possible with decent code", sorry about the confusion.
- panic 11y agoHe mentions a plug-in API in the second linked post. Enums would be a good solution, but they can't be extended with more cases by a plug-in.
- Manishearth 11y agoYeah, that's when you would use trait objects in Rust (and virtual dispatch protocols in Swift?), but you still probably won't use them exactly the same way.
- alexjarvis 11y agoextensions let you do this with enums (as well as protocols): enum Car { case Red case Blue } extension Car { func hello() { print("hello") } } let x = Car.Blue x.hello() https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html https://developer.apple.com/library/ios/documentation/Swift/... edit: although you can't add a new case to the enum.
- Manishearth 11y ago> although you can't add a new case to the enum. That's the point. We all know you can add impls or extensions.
- fauigerzigerk 11y agoI disagree. This is the classic case for protocols. Look what he says: "I’m using a protocol for the Account type. There are classes that conform to the Account protocol: TwitterAccount, AppNetAccount, LinkedInAccount, etc." The important bit is the "etc." at the end. The types of accounts he wants to support is open ended. He also mentions a plug-in API. You can't have third parties come in and extend some algebraic data type. If this is not a case for protocols then there is no such case at all and protocols should be removed from the language.
- Manishearth 11y ago> protocols should be removed from the language. That's a bit harsh. You forget that protocols are useful for generics too. But yes, if they want it to be extensible, protocols would be the way to go. Looking deeper into the issue, it seems like this isn't really an issue with protocols, just an issue with the design of Hashable or Equatable (either allow Equatable to work with protocol objects, or move the Equatable dependency of Hashable to a different kind of Equatable). This sounds like a nitpick, but it's the difference between language and API design, and the latter is often easier to evolve. It also means that this can be fixed by a ProtocolHashSet<T> library which does hashing differently without touching the compiler/stdlib at all.
- captaincrowbar 11y agoTo me this looks like exactly the sort of situation where classical OO inheritance is the right answer. All of those things are kinds of Account and should have a common base class.
- Manishearth 11y agoClassical OO inheritance is the right answer in a language that supports it, though. There may be equally ergonomic ways of doing it in languages with ADTs. (Which may or may not be the case here, depending on extensibility)
- seanalltogether 11y agoOne option available to him is to make Feeds conform to something he can guarantee will be hashable, specifically NSObjectProtocol. The would force all Feed objects to inherit from NSObject and hashability will come along for the ride, but I understand his desire to make things lighter.
- jcizzle 11y agoThis isn't a limitation of Swift, this is someone trying to write Objective-C in Swift. Let's say he gets what he wants - a distinct collection of instances that fulfill the 'Account' contract, but are different types. The approach so far is that to fulfill 'Account' by providing an accountID. That accountID is what indicates if two 'Account's are equal. What if a Twitter account and Hacker News account have the same id? According to this protocol, they are equal. The error message he is seeing about Self requirements is telling him this. That error message doesn't exist because Swift is having a hard time understanding the code, it exists because Swift understands that his code is wrong.
- Manishearth 11y agoWhile I mostly agree, there still should be a way in Swift to make a collection of virtual dispatch objects, including hash-based collections. But this can be done in a separate library since this isn't a language restriction. Might try to write one tomorrow, really.
- jcizzle 11y agoThis is all possible - the issue is that you still must define equality if you want to put it in a collection that cares about equality.
- Manishearth 11y agoSure, and then you redefine it so that it can handle protocol objects (where a things of different concrete types are inequal). You may need a new Equatable/Hashable-esque protocol for this, but it should be doable.
- nathanvanfleet 11y agoYou're missing the point of what he's saying with a pretty dismissive statement. "Swift doesn't do that" is not a strong argument. He specifically goes through why using protocols like that are a useful and powerful tool. He's making an argument about its usefulness and the ease of implementing it. It is a really useful tool! It lets you show the commonalities and mask and private methods in classes. I would say that it would be a loss to not have this. As for the "this might accidentally be seen as equal to something that is a different class" should not be a problem. In objective C isEqual implemented in a standard way is able to differentiate different classes from each other pretty much from the beginning of the method.
- Veedrac 11y agoI don't know Swift, but the problem seems to be that you can't do extension ListItem: Hashable where ListItem is a protocol. That's a little irksome, sure, but why not just do struct ListItemBox { var item: ListItem } func ==(lhs: ListItemBox, rhs: ListItemBox) -> Bool { return lhs.item.id == rhs.item.id } extension ListItemBox: Hashable { var hashValue: Int { return item.id } } var stuff: Set<ListItemBox>; Of course, you should be very careful about cross-type equality here. But that's no easier with any other model either.
- seanalltogether 11y agoI think it can be reduced into simpler terms in fact. In swift you simply can't do var stuff:Set var stuff:Set<Hashable> var stuff:Set<MyProtocol> Sets don't allow for mixed base types, and that's what a protocol would allow. All objects in a set must contain a similar base type. This is actually true in obj-c as well where everything in a set had to derive from NSObject.
- cellularmitosis 11y agoTwo things which might be useful to him: Rob napier's article on type erasure: http://robnapier.net/erasure http://robnapier.net/erasure Alexis Gallagher's talk on PATs and generic constraints: https://youtu.be/XWoNjiSPqI8 https://youtu.be/XWoNjiSPqI8