Gentle Generics – John Sundell at Hacking with Swift Live 2019

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] super hi everybody I am super super happy to be here my name is whoa you see everything's falling apart it's lie rights hacking would Swift live it's exciting so my name is Johnson Dell like Paul said I'm the creator of Swift bison Dell which is a weekly series of articles and a podcast about Swift development but most importantly is that I'm just a swift developer just like you and I'm really really excited to be here today to give you a talk about a topic that is quite dear to my heart which is about generics so Paul asked me to do the opening keynote for this conference and when he did he asked me well John would you like to do this and I said yeah of course that would be a lot of fun and he said well the only thing I needed to do is come up with some lightweight simple topic that we can use to kind of warm up the day you know nothing too complicated give people a little warm-up it's going to be great and I said don't worry Paul I got this covered and what did I come up with generics right so that's gonna be a lot of fun but you know what these are gentle generics so what I want to talk about today is how I think we can deploy generics in a way that is easier to understand and actually make our code simpler not more complicated and now I also think that generics is really key to fundamentally understanding how a lot of how Swift works and also with the new frameworks that Apple are introducing this year like Swift UI and combine which makes heavy heavy use of generics but generics they do have a little bit of a reputation in our community when people hear this word a lot of people think that you know that they are complicated they generate this really hard to read code that it's really only relevant for people who build frameworks it's not really something that we should use an app level code and some people might even go so far to say that using generics is kinda like over engineering let using concrete types is way simpler and just easier to understand and I definitely agree that some uses of generics can definitely be described with these terms there's a way to to implement generics in a way that is really complicated just like any other code but I also believe that there is a lot of ways to use generics in a ways that actually makes code simpler not more complicated and like I mentioned earlier I also think that generics is really much more than just any language feature they're a core fundamental part of Swift itself and a big reason why Swift can work the way it does so if you open up your terminal and you type man Swift which gives you the manual entry for the Swift compiler you're gonna get a description as the first sentence and it reads like this the Swift is a safe fast and expressive general-purpose programming language now you might notice there that the very first word in the sentence is safe and as we all know Swift puts a high high focus on type safety and leveraging the compiler in order to produce more safe code that is less prone to runtime errors and that's a big reason why many of us love Swift because it lets us write code that is more robust we don't like have to worry that we forgot to check that variable if it was nil or not or that we are accidentally passing a string to an argument that accepts an integer or something like that and a big reason why this works in Swift is because of generics if you look at a code sample here we can see that we create an array of strings it contains two elements hello and world and then we try to append the integer 17 to that array now of course this doesn't work the compiler gives us an error it says you can't do this you're trying to put an integer into an array that's supposed to only contain strings and that's awesome because that just removes a whole class of common programming errors I think anyone who ever coded in Objective C or in more dynamic languages like Java scripts have faced this issue whether you accidentally mixed different objects in an array so this is really great and the only reason really that they come Tyler's able to give us this error message right here is because array in Swift is implemented as a generic struct it's a generic over elements where elements is arrays generic type what that means is that inside of the array type itself elements can literally be anything it can be any type any class and enum struct whatever you want you can put it into an array and that's incredibly powerful and also makes it so that these implementation of array itself cannot make any assumptions about what kind of elements that it contains now that's really great also from a design perspective because it keeps the code within array focus on one thing only being an array and the way you can kind of look at this is that array is a generalization of a given concept in this case it's the concept of storing different elements sequentially in memory which is what erase do but equally important is that this concept of being an array can then be specialized into any number of concrete use cases so for example we can create an array of strings now we specialized Jack generic into being bound to the string type and that's great because that enables us to make all those assumptions that the array actually contains strings so the first thing we can really learn about generics over all is that they're all about generalizing a piece of code so it's no longer bound to a single concrete type and then allowing that generic code to then be specialized for any number of use cases which is really cool but of course these days generics is very very very topical and something that we really have to focus on as we're getting new frameworks from Apple that makes heavy use of generics and all those features that they contain and of course one of those exciting new frameworks is swift UI I think everyone here in this room and everyone in the Swift and iOS developer community has this like mixed emotions about Swift UI that it's exciting it's fun it's we're eager to learn it but it's also kind of scary it's something that is really changing the way we're going to write apps it's a big paradigm shift and if you kind of be a little bit scary or stressful looking at all these really huge new changes so let's take a look at a code sample from it from a view that we can build using swift UI so here we're creating a contact list it's a list that's going to display an array of contacts and we're going to implement the body of this Swift UI view now one thing that I really love about Swift UI and I'm really really excited about it it's just how lightweights the syntax is when you use it it really removes a lot of the cruft that we previously needed to build you eyes it just makes it so much simpler so for example if you want to build this list here what we can do is we can start by creating a list struct and then passing in that array of contacts directly to the list and that gives us a closure where we get access to each contact and then we're going to create the view that's going to represent each one so to do that we're going to use the V stack type which is a vertical stack we're going to align all of our elements to the leading edge which for left-to-right languages would mean on the left edge and then we're going to create two pieces of text one for the name of each contact and one for the email address of that contact now this is really cool just imagine how much code you would have to write in UI kit to make this happen you would have to start with a view controller then a UI table data source and a table view itself and sell configuration code and on and on and also like I mentioned earlier the syntax here is incredibly lightweight there's not a lot of symbols there's not a lot of extra words we're really just focusing on what we're trying to achieve with this declarative UI description but if we look a little bit beyond just the surface level of Swift UI and we started asking ourselves some questions for example what is the actual type of this view here you can see here that we're using the some keyword which is a new feature in Swift 5.1 which is called opaque return types it enables us to return any type that conforms to the view protocol as part of this computed property so in this case we're using lists so you might be thinking well since we're returning a list here it should mean that the type of our view is list right we're just passing two contacts in to a list so the type should just be list well not really in fact if you inspect the actual result of this expression this is the type that you get for each which is generic over array which is an Eric over contact and this got an H stack generic over V stack tuple view text text whoo now that's what I call a type name well we can look at another example here we have a little bit something that is a little bit more simple we have a text we are creating a text using one of our contacts names and then we're just changing the accessibility identifier of that text to for example be able to UI test this and then we're also changing the minimum scale factor of this label so that the system will be able to shrink it down to zero zero point five now again what's the type of this expression it should be even more straightforward right we're literally creating a text here and just modifying some properties it should just be a text well not really here is the time that we get modified content generic over modified content generic over text accessibility identifier environment key writing modifier out CG floats all right well you might be thinking now when you hear me say these things that I am kind of making fun of Swift UI or that I'm criticizing it that this is ridiculous wise why do we have all these crazy types and I know that Apple is working on simplifying these types and also improving some of the diagnostics and error messages that we can get from seeing these things but I'm not criticizing Swift UI here actually I think the fact that this is true that we have this simple API that it generates this this type it's just a testament just how awesome Swift you is because if you think about it we are not necessarily seeing all of these types when we're using it all we see is this simple nice declarative API but then under the hood all these complex generics are created for us now this is really powerful because it enables Swift UI itself to retain type safety throughout system it can encode a lot of the things that we use when we modify our views into the type system itself using things like accessibility identifiers and modifiers and other things but at the same time it doesn't leak those implementation details out to us for us we just get a simple elegant top-level API but at the same time under the hood generics is really what's powering everything and I think this is really what generics should be all about generics should make our top-level code easier and simpler it shouldn't make it harder and if they don't do that if they don't make it simpler then generics is most likely the wrong tool for that problem let's take a look at an example that we might face when we're building some of our apps ourselves so let's say that we are writing a drawing application an application where the user is able to put different elements on a canvas so you might have a function called draw and this function here accepts a shape so we pass a shape into it and we're gonna get an image back and to do that we create a drawing context and then for each point in our shape we add that to the context and then finally we return the result of making an image out of our context so this is a really simple and nice function it's just as three lines of body it's it's quite easy to understand but then we start iterating on our app and we want to add more features let's say we don't only want to add support for drawing shapes we want to draw many more things for example we want might want to draw an array of shapes like the user has selected a bunch of different shapes and we want to draw them and an initial idea on how to implement this might be to say well I already have this draw function that lets me draw a single shape let me now take that draw function map over the array of shapes that I get passed in I'm gonna get an array of images back and then I'm just going to write some kind of combine function that takes that array of images and just makes it so that it's just one image now this is again a simple implementation it's just two lines but if we think about it for a little bit it's not very efficient because what we're really doing here is that we're drawing each shape into its own drawing context and we're even generating one image for each shape and then we have to do the work to combine all those images into one and that's really unnecessary computation we shouldn't have to do and it might also slow down our UI and if the user selects let's say a thousand shapes and wants to draw all of those our app might start crashing or or behave in really bad ways so that's a bit of a problem but equally important is that if we want to keep iterating on this draw function and add even support for more things as well let's say that we want to be able to draw text we want to be able to draw charts or icons or all sorts of other things you can imagine in a drawing application we're starting to see a problem here we're going to have to repeat our work over and over again and create different overloads and different versions of this draw function in order to make all of these different things happen and if we think back a little bit to the beginning where we looked at array where array was a generalization of the concept of storing elements where you can then specialize that concept to a given use case for example strings isn't the same really true here in our app where we are also dealing with a generalized concept of drawing that we don't want to specialize for shapes and icons and images and all sorts of things that's really what we're dealing with here but the problem in our current implementation is that the draw functions that we've created they both act as the generalization of the drawing concept itself and they also are the specialization for it it's all mashed into one and that's really what's causing our problem we have a conflict which should ideally be more generalized so whether we can use many different things but we have instead opted to make it concrete for each use case and duplicating a lot of code so let's take a look at how it could fix this the first thing we could do is we could take our drawing context type and turn it into a protocol instead and the only requirements we will have for this protocol is that we can create a new drawing concept context just with an empty initializer and that we can create an image out of a drawing context then what we can do is we can take out all of our drawing code and make a protocol out of that called drawable drawable will be a generic protocol and the reason it's generic is because it has this Associated type and the Associated type here has a type constraint that says that in order to create a context you need to conform to the drawing context protocol now this combination here is really powerful because it enables any drawable type whether that's a shape and image a text or anything else to define what kind of context that it wants to be drawn in and that's what makes it generic but at the same time we've added a constraint on that generic to say it needs to conform to drawing context and that way when we are writing code against it we know that we will always have an empty initializer and we will always be able to make an image out of each drawing context now that's really cool because it enables us to take all those drawing functions that we had before all those different overloads and all that complexity and really reduce it to just a single function we're gonna call it image from just to make the naming really clear you're taking and you're taking any drawable here and again we have a generic constraint here to say that this function can be used with a type that we call D and D can be any type that you want that's again the power of generics is a generalized concept but you need to conform to drawable so pass in any drawable that you want here now just like how we talked about earlier how array couldn't make any assumptions about what kind of elements it stores the same is true for our function here as well we cannot assume that we're drawing a shape or we're drawing a text or any other concrete type we just know that we're drawing any drawable but that's great because that way we can create this implementation which is extremely generic where we can create a context using the Associated type from the drawable protocol and then we can just tell the drawable to draw itself in that context and finally because we have that make image requirements we can call our context and make an image out of that and we can reuse this function for any drawable that we can think of either now all the types you already have or in the future which is pretty cool so what we can do is we can go back now to our existing code for example our shape and make it conform to drawable implement the draw and context function and they can decide what kind of context it wants to be drawn in for example since it's a shape it might be drawn using vector graphics so here we put that same code we have before of iterating through the points and adding them to the context but we can also do the same for any other type that we have so for example we can extend our text type to say that this is drawable but in a text drawing context which supports things like fonts and strings and text specific properties but both of these types can then be used with the same function that generates an image from them which is really really powerful so what we've done here really is we've generalized the concept of drawing into a drawable protocol and then we've created specializations of that such as shape text and image which can each define their own context that they're drawing in now this gives us the power of having a generalized concept which doesn't make any assumptions about what kind of drawing it's doing but at the same time it gives us the flexibility to implement as many of them as many drawables as we want because it's a protocol and it's completely generic so another way of looking at what we've essentially done here is that we've taken a bunch of different use cases like drawing images texts shapes etc and we've built a common shared API on top of that now having a shared API is not only great for code elegance kind of sake it's also great for or getting into a new codebase if you think about it before we had all these different drawing functions where if someone new joined our team we would have to go through each and every one of them and explain what each drawing function does now we can just point them to this image from function and that's all they need to know in order to use our drawing code which is really cool but also equally important is that we don't really have this shared API but once we have that we can actually build really powerful abstractions on top of that shared API because we have that common foundation so a few different things that we can do to make our code even easier to work with and build convenience api's is that we can do something like we can extend array which again isn't a generic so we can say that array will now conform to drawable when the elements in that array also conform to drawable and then we can implement draw for an array but not for any array just for the ones that contains drawable elements and then we can retain the type safety just how swift UI use generics to retain the type safety we can do the same thing here where we are making sure that we're drawing in the same context type in for each of our elements as the elements themselves are and here now you can see that we've actually through this fix the problem that we originally had where we were previously drawing an array of shapes using an N number of drawing contexts but here we can pass the same context and draw all of the elements in the array into the same context and just generate one image which also will help performance but the cool thing is that even though this code here might look a little bit more complex it's an implementation detail as the API user as someone using our code they don't need to know about these details they can just create an array of shapes and then just say image from onit's and get an image back it's super super simple on the top level and that's really where generics really shine another thing that we could do is that we could create more generic types that helps us do drawing in more powerful ways for example we could implement a layer type that applies some kind of transform to an array of elements and that again conforms to drawable the cool thing about that is that we can introduce all these new types and we can still use that same image from function with any of them because we don't need to keep implementing the same thing so what we can see here through this example is that generics are really great we want to reuse some of our core logic for different use cases and they can let us build these powerful shared abstractions that can be applied to any of those use cases the same way as when you extend array or dictionary you can have that apply to any code that uses them the same thing is true here since we've built that shared layer using generics now when you hear this talk here and you've seen some of these examples it might remind you of a principle or a way of working that has become really really popular in the Swift developer community and popular is a is a is a good way of describing it because what I'm talking about is of course pop and pop is what many people refer to as protocol oriented programming now protocol origin oriented programming and generics are very very highly related you usually use protocols to define some kind of shared API that you're then using with generics and I think protocol or into programming is great but I think sometimes when you hear this word or this phrase protocol or into programming when you hear people talk about it when you hear talks such as this one which you know talks about how great generics are and how they can read it let us reduce our code and make it so much simpler it's easy sometimes to get the impression that this should be something that we should apply everywhere and I've worked with many teams where that has been the case where protocol oriented programming has become so popular that people have started to create protocols literally everywhere for every single type there's a protocol for every API there's a protocol just even if we might not need it right now we just create a protocol because it's protocol oriented programming right well you know this common expression which is that if all you have is a hammer everything looks like a nail well I think that switch developer equivalent of that is if all you have is generics then everything looks like it should be a protocol as an ancient Swift proverb very very old found in the ancient scriptures so just to look at an example of where I think these things can go wrong where generics might not be the right tool for the problem or where protocols might not be the right abstraction is something like you look at one of your models like you have a contact model like we were using before when we were drawing our contact list it's a very simple type it's as a name and an email but you might be tempted to say well you know what I think I can generalize the concept of being a contact so I'm gonna create a contactable protocol because of course all protocols needs to end with a blur right and then we have a name email here it's defined in this beautiful protocol oriented way and this is going to be awesome because now I can have this perfect vision of the future another this beautiful system I built I will dream about it I will have like a list of all protocol it's gonna have a contact able element and then I'm gonna have a list manager and it's going to be able to take any array of contacts which our contact table and I can just render them it's going to be so so great well these things can be fun to do as a hobby project or to learn different language features or just to impress your co-workers around how many Swift features you know how to use but it's not really great from a perspective of making our code simpler so if you look at it what's the reason that generics is not really the right tool for the job here well again I want to use that simple flow chart I've been showing you a few times now which is like generics are great when we want to generalize a problem and then create multiple specializations of it so in this case we have our contact able generalization but it's just about being a contact in our specialization we only have one and that's also being a contact so it doesn't really make any sense the generalization and the specialization are exactly the same and I think this is a good thing to keep in mind when deciding whether or not to deploy protocols or generics or any of these more advanced features is that generics there are a great tool they really are but they're not a goal the goal of our code should not be less use as many generics as possible there'll be a pretty crazy goal so instead making code generics should be in reaction to an actual demand something that we've seen as a problem in our in our code base that we want to refactor to support not because we think we might reuse our logic sometime in an ideal version of the future because we spend all of our time optimizing for the future chances are high that that future will actually never arrive and we've just done all that optimization work well pretty much for nothing so when we think about protocol oriented programming going forward I think it's important to stick to the fact that it should be oriented around protocols but not obsessed with protocols that's I think what's really important to keep in mind so to sum up this morning talk about a very lightweight topic I think that generics they're truly great we want to model a concept a given concept that's a generic one for multiple use cases so think of a ray think of this drawing function we have think of any use case in your app where you have multiple use cases for the same functionality which could be generalized into a more generic concept that's where generics are really good for the goal of deploying generics should be to make our top-level code simpler not harder or more complicated again it's not about showing off how many swift features you know how to use it's about making our code better and generics can be a great way to achieve that but it's not a silver bullet it's not something that should be used for every single use case especially if we don't have those multiple multiple different use cases and then finally when used in the right situations generics can be incredibly powerful and they can let us reuse both code and concepts throughout shared abstractions so just like how we created an image from function that could be used with any drawable that didn't only enable us to share code you know unable people on our team to share knowledge easier because they only need to learn that one function instead of having to learn all these different variations of it and I think if we keep these three things in mind what we can do is we can make generics a little bit easier to use and a little bit more gentle thank you very much [Applause]
Info
Channel: Paul Hudson
Views: 15,653
Rating: undefined out of 5
Keywords: ios, swift, swiftui, generics, xcode, programming, talk, tutorial
Id: y4qFRLp_JNM
Channel Id: undefined
Length: 29min 26sec (1766 seconds)
Published: Thu Jul 18 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.